Skip to content

Commit 4feae11

Browse files
committed
Refactor channel management documentation and update database schema
- Updated documentation for channel API endpoints in Japanese, Korean, and Chinese to improve clarity and consistency. - Enhanced channel operations section to include detailed request and response formats, including error handling. - Revised database schema to include new fields for device restrictions and channel configurations. - Modified Supabase types to reflect changes in the database schema, adding support for device-specific settings.
1 parent 2e8e0f6 commit 4feae11

File tree

11 files changed

+1836
-560
lines changed

11 files changed

+1836
-560
lines changed

src/content/docs/de/docs/plugins/updater/self-hosted/handling-channels.mdx

Lines changed: 183 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Channels are a core mechanism for managing app updates in Capgo. In self-hosted
1414

1515
Channels allow you to:
1616
- **Control update distribution**: Assign different app versions to different user groups
17-
- **A/B testing**: Test new features with specific user segments
17+
- **A/B testing**: Test new features with specific user segments
1818
- **Staged rollouts**: Gradually deploy updates to minimize risk
1919
- **Environment separation**: Separate development, staging, and production updates
2020

@@ -36,9 +36,9 @@ Configure the channel endpoint URL in your `capacitor.config.json`:
3636

3737
The plugin performs different channel operations that your endpoint needs to handle:
3838

39-
### 1. Get Channel (GET Request)
39+
### 1. List Compatible Channels (GET Request)
4040

41-
When the plugin calls `getChannel()`, it sends a GET request to retrieve the device's current channel assignment.
41+
When the plugin calls `listChannels()`, it sends a GET request to retrieve all channels that are compatible with the device. This returns channels that match the device's environment (dev/prod, emulator/real device) and allow either public access or self-assignment.
4242

4343
#### Request Format
4444
```typescript
@@ -48,7 +48,59 @@ When the plugin calls `getChannel()`, it sends a GET request to retrieve the dev
4848
"Content-Type": "application/json"
4949
}
5050

51-
// Query parameters or body:
51+
// Query parameters:
52+
interface ListChannelsRequest {
53+
app_id: string
54+
platform: "ios" | "android"
55+
is_emulator: boolean
56+
is_prod: boolean
57+
key_id?: string
58+
}
59+
```
60+
61+
#### Response Format
62+
```json
63+
[
64+
{
65+
"id": 1,
66+
"name": "production",
67+
"public": true,
68+
"allow_self_set": false
69+
},
70+
{
71+
"id": 2,
72+
"name": "beta",
73+
"public": false,
74+
"allow_self_set": true
75+
}
76+
]
77+
```
78+
79+
#### Understanding Channel Types
80+
81+
The response includes two important flags for each channel:
82+
83+
- **`public: true`**: This is a **default channel**. Devices cannot self-assign to it using `setChannel()`. Instead, if a device removes its channel assignment (using `unsetChannel()`), it will automatically receive updates from this public channel if it matches the device's conditions.
84+
85+
- **`allow_self_set: true`**: This is a **self-assignable channel**. Devices can explicitly assign themselves to this channel using `setChannel()`. This is useful for beta testing, A/B testing, or allowing users to opt-in to specific update tracks.
86+
87+
:::note
88+
A channel can be either `public` OR `allow_self_set`, but typically not both. Public channels serve as the default fallback, while self-assignable channels require explicit opt-in.
89+
:::
90+
91+
### 2. Get Channel (PUT Request)
92+
93+
When the plugin calls `getChannel()`, it sends a PUT request to retrieve the device's current channel assignment.
94+
95+
#### Request Format
96+
```typescript
97+
// PUT /api/channel_self
98+
// Headers:
99+
{
100+
"Content-Type": "application/json"
101+
}
102+
103+
// Body:
52104
interface GetChannelRequest {
53105
device_id: string
54106
app_id: string
@@ -57,6 +109,10 @@ interface GetChannelRequest {
57109
version_build: string
58110
version_code: string
59111
version_name: string
112+
is_emulator: boolean
113+
is_prod: boolean
114+
defaultChannel?: string
115+
channel?: string // For newer plugin versions, contains local channel override
60116
}
61117
```
62118

@@ -71,7 +127,7 @@ interface GetChannelRequest {
71127
}
72128
```
73129

74-
### 2. Set Channel (POST Request)
130+
### 3. Set Channel (POST Request)
75131

76132
When the plugin calls `setChannel()`, it sends a POST request to assign the device to a specific channel.
77133

@@ -87,6 +143,8 @@ interface SetChannelRequest {
87143
version_build: string
88144
version_code: string
89145
version_name: string
146+
is_emulator: boolean
147+
is_prod: boolean
90148
}
91149
```
92150

@@ -99,7 +157,29 @@ interface SetChannelRequest {
99157
}
100158
```
101159

102-
### 3. Unset Channel (DELETE Request)
160+
#### Error Cases
161+
162+
When a device tries to assign itself to a **public channel** (one with `public: true`), your endpoint should return an error:
163+
164+
```json
165+
{
166+
"status": "error",
167+
"error": "public_channel_self_set_not_allowed",
168+
"message": "This channel is public and does not allow device self-assignment. Unset the channel and the device will automatically use the public channel."
169+
}
170+
```
171+
172+
When a device tries to assign itself to a channel that doesn't allow self-assignment:
173+
174+
```json
175+
{
176+
"status": "error",
177+
"error": "channel_self_set_not_allowed",
178+
"message": "This channel does not allow devices to self associate"
179+
}
180+
```
181+
182+
### 4. Unset Channel (DELETE Request)
103183

104184
When the plugin calls `unsetChannel()`, it sends a DELETE request to remove the device's channel assignment.
105185

@@ -144,20 +224,20 @@ interface ChannelResponse {
144224
export const handler = async (event) => {
145225
const method = event.httpMethod || event.method
146226
const body = JSON.parse(event.body || '{}') as ChannelRequest
147-
227+
148228
const { device_id, app_id, channel, platform } = body
149229

150230
try {
151231
switch (method) {
152232
case 'GET':
153233
return await getDeviceChannel(device_id, app_id)
154-
234+
155235
case 'POST':
156236
return await setDeviceChannel(device_id, app_id, channel!, platform)
157-
237+
158238
case 'DELETE':
159239
return await unsetDeviceChannel(device_id, app_id)
160-
240+
161241
default:
162242
return {
163243
status: "error",
@@ -175,15 +255,15 @@ export const handler = async (event) => {
175255
async function getDeviceChannel(deviceId: string, appId: string): Promise<ChannelResponse> {
176256
// Query your database for device channel assignment
177257
const assignment = await database.getDeviceChannel(deviceId, appId)
178-
258+
179259
if (assignment) {
180260
return {
181261
status: "ok",
182262
channel: assignment.channel,
183263
allowSet: assignment.allowSelfAssign
184264
}
185265
}
186-
266+
187267
// Return default channel if no assignment found
188268
return {
189269
status: "ok",
@@ -193,46 +273,46 @@ async function getDeviceChannel(deviceId: string, appId: string): Promise<Channe
193273
}
194274

195275
async function setDeviceChannel(
196-
deviceId: string,
197-
appId: string,
198-
channel: string,
276+
deviceId: string,
277+
appId: string,
278+
channel: string,
199279
platform: string
200280
): Promise<ChannelResponse> {
201281
// Validate channel exists and allows self-assignment
202282
const channelConfig = await database.getChannelConfig(channel, appId)
203-
283+
204284
if (!channelConfig) {
205285
return {
206286
status: "error",
207287
error: "Channel not found"
208288
}
209289
}
210-
290+
211291
if (!channelConfig.allowDeviceSelfSet) {
212292
return {
213293
status: "error",
214294
error: "Channel does not allow self-assignment"
215295
}
216296
}
217-
297+
218298
// Check platform restrictions
219299
if (platform === "ios" && !channelConfig.ios) {
220300
return {
221-
status: "error",
301+
status: "error",
222302
error: "Channel not available for iOS"
223303
}
224304
}
225-
305+
226306
if (platform === "android" && !channelConfig.android) {
227307
return {
228308
status: "error",
229-
error: "Channel not available for Android"
309+
error: "Channel not available for Android"
230310
}
231311
}
232-
312+
233313
// Save the assignment
234314
await database.setDeviceChannel(deviceId, appId, channel)
235-
315+
236316
return {
237317
status: "ok",
238318
message: "Device assigned to channel successfully"
@@ -242,7 +322,7 @@ async function setDeviceChannel(
242322
async function unsetDeviceChannel(deviceId: string, appId: string): Promise<ChannelResponse> {
243323
// Remove device channel assignment
244324
await database.removeDeviceChannel(deviceId, appId)
245-
325+
246326
return {
247327
status: "ok",
248328
message: "Device channel assignment removed"
@@ -258,22 +338,67 @@ Your channel system should support these configuration options:
258338
interface ChannelConfig {
259339
name: string
260340
appId: string
261-
341+
262342
// Platform targeting
263-
ios: boolean
264-
android: boolean
265-
266-
// Device restrictions
267-
allowDeviceSelfSet: boolean // Allow setChannel() calls
268-
allowEmulator: boolean
269-
allowDev: boolean // Allow development builds
270-
343+
ios: boolean // Allow updates to iOS devices
344+
android: boolean // Allow updates to Android devices
345+
346+
// Device type restrictions
347+
allow_emulator: boolean // Allow updates on emulator/simulator devices
348+
allow_device: boolean // Allow updates on real/physical devices
349+
350+
// Build type restrictions
351+
allow_dev: boolean // Allow updates on development builds (is_prod=false)
352+
allow_prod: boolean // Allow updates on production builds (is_prod=true)
353+
354+
// Channel assignment
355+
public: boolean // Default channel - devices fall back to this when no override
356+
allowDeviceSelfSet: boolean // Allow devices to self-assign via setChannel()
357+
271358
// Update policies
272359
disableAutoUpdate: "major" | "minor" | "version_number" | "none"
273360
disableAutoUpdateUnderNative: boolean
274-
275-
// Assignment
276-
isDefault: boolean // Default channel for new devices
361+
}
362+
```
363+
364+
### Device Filtering Logic
365+
366+
When listing compatible channels (GET request), you should filter channels based on these conditions:
367+
368+
1. **Platform check**: Channel must allow the device's platform (`ios` or `android`)
369+
2. **Device type check**:
370+
- If `is_emulator=true`: Channel must have `allow_emulator=true`
371+
- If `is_emulator=false`: Channel must have `allow_device=true`
372+
3. **Build type check**:
373+
- If `is_prod=true`: Channel must have `allow_prod=true`
374+
- If `is_prod=false`: Channel must have `allow_dev=true`
375+
4. **Visibility check**: Channel must be either `public=true` OR `allow_device_self_set=true`
376+
377+
```typescript
378+
// Example filtering logic
379+
function getCompatibleChannels(
380+
platform: 'ios' | 'android',
381+
isEmulator: boolean,
382+
isProd: boolean,
383+
channels: ChannelConfig[]
384+
): ChannelConfig[] {
385+
return channels.filter(channel => {
386+
// Platform check
387+
if (!channel[platform]) return false
388+
389+
// Device type check
390+
if (isEmulator && !channel.allow_emulator) return false
391+
if (!isEmulator && !channel.allow_device) return false
392+
393+
// Build type check
394+
if (isProd && !channel.allow_prod) return false
395+
if (!isProd && !channel.allow_dev) return false
396+
397+
// Must be accessible (public or self-assignable)
398+
if (!channel.public && !channel.allowDeviceSelfSet) return false
399+
400+
return true
401+
})
277402
}
278403
```
279404

@@ -287,23 +412,36 @@ CREATE TABLE channels (
287412
id SERIAL PRIMARY KEY,
288413
name VARCHAR(255) NOT NULL,
289414
app_id VARCHAR(255) NOT NULL,
415+
416+
-- Platform targeting
290417
ios BOOLEAN DEFAULT true,
291418
android BOOLEAN DEFAULT true,
292-
allow_device_self_set BOOLEAN DEFAULT false,
293-
allow_emulator BOOLEAN DEFAULT true,
294-
allow_dev BOOLEAN DEFAULT true,
419+
420+
-- Device type restrictions
421+
allow_emulator BOOLEAN DEFAULT true, -- Allow emulator/simulator devices
422+
allow_device BOOLEAN DEFAULT true, -- Allow real/physical devices
423+
424+
-- Build type restrictions
425+
allow_dev BOOLEAN DEFAULT true, -- Allow development builds
426+
allow_prod BOOLEAN DEFAULT true, -- Allow production builds
427+
428+
-- Channel assignment
429+
public BOOLEAN DEFAULT false, -- Default channel (fallback)
430+
allow_device_self_set BOOLEAN DEFAULT false, -- Allow self-assignment
431+
432+
-- Update policies
295433
disable_auto_update VARCHAR(50) DEFAULT 'none',
296434
disable_auto_update_under_native BOOLEAN DEFAULT false,
297-
is_default BOOLEAN DEFAULT false,
435+
298436
created_at TIMESTAMP DEFAULT NOW(),
299437
UNIQUE(name, app_id)
300438
);
301439

302-
-- Device channel assignments table
440+
-- Device channel assignments table
303441
CREATE TABLE device_channels (
304442
id SERIAL PRIMARY KEY,
305443
device_id VARCHAR(255) NOT NULL,
306-
app_id VARCHAR(255) NOT NULL,
444+
app_id VARCHAR(255) NOT NULL,
307445
channel_name VARCHAR(255) NOT NULL,
308446
assigned_at TIMESTAMP DEFAULT NOW(),
309447
UNIQUE(device_id, app_id)
@@ -323,7 +461,7 @@ Handle common error scenarios:
323461

324462
// Self-assignment not allowed
325463
{
326-
"status": "error",
464+
"status": "error",
327465
"error": "Channel does not allow device self-assignment"
328466
}
329467

@@ -357,10 +495,10 @@ async function getUpdateForDevice(deviceId: string, appId: string) {
357495
// Get device's channel assignment
358496
const channelAssignment = await getDeviceChannel(deviceId, appId)
359497
const channel = channelAssignment.channel || 'production'
360-
498+
361499
// Get the version assigned to this channel
362500
const channelVersion = await getChannelVersion(channel, appId)
363-
501+
364502
return {
365503
version: channelVersion.version,
366504
url: channelVersion.url,

0 commit comments

Comments
 (0)