@capgo/capacitor-updater 8.49.0 → 8.49.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -6
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +110 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +44 -5
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +45 -30
- package/android/src/main/java/ee/forgr/capacitor_updater/ThreeFingerPinchDetector.java +217 -63
- package/dist/docs.json +3 -3
- package/dist/esm/definitions.d.ts +15 -6
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +87 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +32 -6
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +22 -13
- package/package.json +1 -1
|
@@ -6,13 +6,22 @@
|
|
|
6
6
|
|
|
7
7
|
package ee.forgr.capacitor_updater;
|
|
8
8
|
|
|
9
|
+
import android.view.ActionMode;
|
|
10
|
+
import android.view.KeyEvent;
|
|
11
|
+
import android.view.KeyboardShortcutGroup;
|
|
12
|
+
import android.view.Menu;
|
|
13
|
+
import android.view.MenuItem;
|
|
9
14
|
import android.view.MotionEvent;
|
|
15
|
+
import android.view.SearchEvent;
|
|
10
16
|
import android.view.View;
|
|
11
|
-
import
|
|
17
|
+
import android.view.Window;
|
|
18
|
+
import android.view.WindowManager;
|
|
19
|
+
import android.view.accessibility.AccessibilityEvent;
|
|
12
20
|
import com.getcapacitor.BridgeActivity;
|
|
13
|
-
import java.lang.
|
|
21
|
+
import java.lang.ref.WeakReference;
|
|
22
|
+
import java.util.List;
|
|
14
23
|
|
|
15
|
-
public class ThreeFingerPinchDetector
|
|
24
|
+
public class ThreeFingerPinchDetector {
|
|
16
25
|
|
|
17
26
|
public interface Listener {
|
|
18
27
|
void onThreeFingerPinchDetected();
|
|
@@ -24,9 +33,10 @@ public class ThreeFingerPinchDetector implements View.OnTouchListener {
|
|
|
24
33
|
|
|
25
34
|
private final Listener listener;
|
|
26
35
|
private final Logger logger;
|
|
27
|
-
private
|
|
28
|
-
private
|
|
29
|
-
private
|
|
36
|
+
private Window targetWindow;
|
|
37
|
+
private Window.Callback previousWindowCallback;
|
|
38
|
+
private Window.Callback windowCallback;
|
|
39
|
+
private WeakReference<ThreeFingerPinchDetector> detectorReference;
|
|
30
40
|
private float initialSpan = 0;
|
|
31
41
|
private boolean tracking = false;
|
|
32
42
|
private boolean triggered = false;
|
|
@@ -38,74 +48,68 @@ public class ThreeFingerPinchDetector implements View.OnTouchListener {
|
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
public void start(BridgeActivity activity) {
|
|
41
|
-
if (
|
|
51
|
+
if (targetWindow != null) {
|
|
42
52
|
stop();
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
view = bridge.getWebView();
|
|
49
|
-
}
|
|
50
|
-
if (view == null && activity.getWindow() != null) {
|
|
51
|
-
view = activity.getWindow().getDecorView().getRootView();
|
|
52
|
-
}
|
|
53
|
-
if (view == null) {
|
|
54
|
-
logger.warn("Three finger pinch detector could not find a target view");
|
|
55
|
+
Window window = activity.getWindow();
|
|
56
|
+
if (window == null) {
|
|
57
|
+
logger.warn("Three finger pinch detector could not find a target window");
|
|
55
58
|
return;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
this.targetWindow = window;
|
|
62
|
+
this.previousWindowCallback = window.getCallback();
|
|
63
|
+
this.detectorReference = new WeakReference<>(this);
|
|
64
|
+
this.windowCallback = new PinchWindowCallback(this.previousWindowCallback, this.detectorReference);
|
|
65
|
+
window.setCallback(this.windowCallback);
|
|
66
|
+
logger.info("Three finger pinch detector installed on activity window");
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
public void stop() {
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
targetView.setOnTouchListener(previousOnTouchListener);
|
|
70
|
+
if (targetWindow != null) {
|
|
71
|
+
if (windowCallback instanceof PinchWindowCallback) {
|
|
72
|
+
((PinchWindowCallback) windowCallback).disable();
|
|
71
73
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
if (detectorReference != null) {
|
|
75
|
+
detectorReference.clear();
|
|
76
|
+
}
|
|
77
|
+
if (targetWindow.getCallback() == windowCallback) {
|
|
78
|
+
targetWindow.setCallback(previousWindowCallback);
|
|
79
|
+
}
|
|
80
|
+
targetWindow = null;
|
|
81
|
+
previousWindowCallback = null;
|
|
82
|
+
windowCallback = null;
|
|
83
|
+
detectorReference = null;
|
|
75
84
|
}
|
|
76
85
|
reset();
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
|
|
80
|
-
public boolean onTouch(View view, MotionEvent event) {
|
|
81
|
-
boolean consumedByPreviousListener = false;
|
|
82
|
-
if (previousOnTouchListener != null && previousOnTouchListener != this) {
|
|
83
|
-
consumedByPreviousListener = previousOnTouchListener.onTouch(view, event);
|
|
84
|
-
}
|
|
85
|
-
|
|
88
|
+
private void handleTouch(MotionEvent event) {
|
|
86
89
|
int action = event.getActionMasked();
|
|
87
90
|
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
|
|
88
91
|
reset();
|
|
89
|
-
return
|
|
92
|
+
return;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
if (event.getPointerCount() != REQUIRED_POINTER_COUNT) {
|
|
93
96
|
if (action == MotionEvent.ACTION_POINTER_DOWN) {
|
|
94
97
|
reset();
|
|
95
98
|
}
|
|
96
|
-
return
|
|
99
|
+
return;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
float span = calculateSpan(event);
|
|
100
103
|
if (span <= 0) {
|
|
101
|
-
return
|
|
104
|
+
return;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
if (!tracking || action == MotionEvent.ACTION_POINTER_DOWN) {
|
|
105
108
|
initialSpan = span;
|
|
106
109
|
tracking = true;
|
|
107
110
|
triggered = false;
|
|
108
|
-
|
|
111
|
+
logger.info("Three finger pinch tracking started");
|
|
112
|
+
return;
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
if (!triggered && Math.abs(span - initialSpan) / initialSpan >= MIN_SCALE_DELTA) {
|
|
@@ -113,13 +117,12 @@ public class ThreeFingerPinchDetector implements View.OnTouchListener {
|
|
|
113
117
|
if (currentTime - lastPinchTime > PINCH_TIMEOUT) {
|
|
114
118
|
triggered = true;
|
|
115
119
|
lastPinchTime = currentTime;
|
|
120
|
+
logger.info("Three finger pinch threshold reached");
|
|
116
121
|
if (listener != null) {
|
|
117
122
|
listener.onThreeFingerPinchDetected();
|
|
118
123
|
}
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
|
-
|
|
122
|
-
return consumedByPreviousListener;
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
private float calculateSpan(MotionEvent event) {
|
|
@@ -141,29 +144,180 @@ public class ThreeFingerPinchDetector implements View.OnTouchListener {
|
|
|
141
144
|
return totalDistance / REQUIRED_POINTER_COUNT;
|
|
142
145
|
}
|
|
143
146
|
|
|
144
|
-
private View.OnTouchListener getCurrentOnTouchListener(View view) {
|
|
145
|
-
try {
|
|
146
|
-
Field listenerInfoField = View.class.getDeclaredField("mListenerInfo");
|
|
147
|
-
listenerInfoField.setAccessible(true);
|
|
148
|
-
Object listenerInfo = listenerInfoField.get(view);
|
|
149
|
-
if (listenerInfo == null) {
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
Field onTouchListenerField = listenerInfo.getClass().getDeclaredField("mOnTouchListener");
|
|
153
|
-
onTouchListenerField.setAccessible(true);
|
|
154
|
-
Object listener = onTouchListenerField.get(listenerInfo);
|
|
155
|
-
if (listener instanceof View.OnTouchListener) {
|
|
156
|
-
return (View.OnTouchListener) listener;
|
|
157
|
-
}
|
|
158
|
-
} catch (ReflectiveOperationException | RuntimeException exception) {
|
|
159
|
-
logger.warn("Three finger pinch detector could not inspect the current touch listener: " + exception.getMessage());
|
|
160
|
-
}
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
147
|
private void reset() {
|
|
165
148
|
initialSpan = 0;
|
|
166
149
|
tracking = false;
|
|
167
150
|
triggered = false;
|
|
168
151
|
}
|
|
152
|
+
|
|
153
|
+
private static class PinchWindowCallback implements Window.Callback {
|
|
154
|
+
|
|
155
|
+
private final Window.Callback delegate;
|
|
156
|
+
private final WeakReference<ThreeFingerPinchDetector> detectorReference;
|
|
157
|
+
private boolean enabled = true;
|
|
158
|
+
|
|
159
|
+
PinchWindowCallback(Window.Callback delegate, WeakReference<ThreeFingerPinchDetector> detectorReference) {
|
|
160
|
+
this.delegate = delegate;
|
|
161
|
+
this.detectorReference = detectorReference;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
void disable() {
|
|
165
|
+
enabled = false;
|
|
166
|
+
if (detectorReference != null) {
|
|
167
|
+
detectorReference.clear();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@Override
|
|
172
|
+
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
173
|
+
return delegate != null && delegate.dispatchKeyEvent(event);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Override
|
|
177
|
+
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
|
|
178
|
+
return delegate != null && delegate.dispatchKeyShortcutEvent(event);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@Override
|
|
182
|
+
public boolean dispatchTouchEvent(MotionEvent event) {
|
|
183
|
+
boolean handled = delegate != null && delegate.dispatchTouchEvent(event);
|
|
184
|
+
if (enabled) {
|
|
185
|
+
ThreeFingerPinchDetector detector = detectorReference == null ? null : detectorReference.get();
|
|
186
|
+
if (detector != null) {
|
|
187
|
+
detector.handleTouch(event);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return handled;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@Override
|
|
194
|
+
public boolean dispatchTrackballEvent(MotionEvent event) {
|
|
195
|
+
return delegate != null && delegate.dispatchTrackballEvent(event);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@Override
|
|
199
|
+
public boolean dispatchGenericMotionEvent(MotionEvent event) {
|
|
200
|
+
return delegate != null && delegate.dispatchGenericMotionEvent(event);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@Override
|
|
204
|
+
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
|
205
|
+
return delegate != null && delegate.dispatchPopulateAccessibilityEvent(event);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@Override
|
|
209
|
+
public View onCreatePanelView(int featureId) {
|
|
210
|
+
return delegate == null ? null : delegate.onCreatePanelView(featureId);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@Override
|
|
214
|
+
public boolean onCreatePanelMenu(int featureId, Menu menu) {
|
|
215
|
+
return delegate != null && delegate.onCreatePanelMenu(featureId, menu);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@Override
|
|
219
|
+
public boolean onPreparePanel(int featureId, View view, Menu menu) {
|
|
220
|
+
return delegate != null && delegate.onPreparePanel(featureId, view, menu);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@Override
|
|
224
|
+
public boolean onMenuOpened(int featureId, Menu menu) {
|
|
225
|
+
return delegate != null && delegate.onMenuOpened(featureId, menu);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@Override
|
|
229
|
+
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
|
230
|
+
return delegate != null && delegate.onMenuItemSelected(featureId, item);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@Override
|
|
234
|
+
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
|
|
235
|
+
if (delegate != null) {
|
|
236
|
+
delegate.onWindowAttributesChanged(attrs);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@Override
|
|
241
|
+
public void onContentChanged() {
|
|
242
|
+
if (delegate != null) {
|
|
243
|
+
delegate.onContentChanged();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@Override
|
|
248
|
+
public void onWindowFocusChanged(boolean hasFocus) {
|
|
249
|
+
if (delegate != null) {
|
|
250
|
+
delegate.onWindowFocusChanged(hasFocus);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@Override
|
|
255
|
+
public void onAttachedToWindow() {
|
|
256
|
+
if (delegate != null) {
|
|
257
|
+
delegate.onAttachedToWindow();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@Override
|
|
262
|
+
public void onDetachedFromWindow() {
|
|
263
|
+
if (delegate != null) {
|
|
264
|
+
delegate.onDetachedFromWindow();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@Override
|
|
269
|
+
public void onPanelClosed(int featureId, Menu menu) {
|
|
270
|
+
if (delegate != null) {
|
|
271
|
+
delegate.onPanelClosed(featureId, menu);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@Override
|
|
276
|
+
public boolean onSearchRequested() {
|
|
277
|
+
return delegate != null && delegate.onSearchRequested();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@Override
|
|
281
|
+
public boolean onSearchRequested(SearchEvent searchEvent) {
|
|
282
|
+
return delegate != null && delegate.onSearchRequested(searchEvent);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@Override
|
|
286
|
+
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
|
|
287
|
+
return delegate == null ? null : delegate.onWindowStartingActionMode(callback);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@Override
|
|
291
|
+
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
|
|
292
|
+
return delegate == null ? null : delegate.onWindowStartingActionMode(callback, type);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@Override
|
|
296
|
+
public void onActionModeStarted(ActionMode mode) {
|
|
297
|
+
if (delegate != null) {
|
|
298
|
+
delegate.onActionModeStarted(mode);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@Override
|
|
303
|
+
public void onActionModeFinished(ActionMode mode) {
|
|
304
|
+
if (delegate != null) {
|
|
305
|
+
delegate.onActionModeFinished(mode);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@Override
|
|
310
|
+
public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
|
|
311
|
+
if (delegate != null) {
|
|
312
|
+
delegate.onProvideKeyboardShortcuts(data, menu, deviceId);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@Override
|
|
317
|
+
public void onPointerCaptureChanged(boolean hasCapture) {
|
|
318
|
+
if (delegate != null) {
|
|
319
|
+
delegate.onPointerCaptureChanged(hasCapture);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
169
323
|
}
|
package/dist/docs.json
CHANGED
|
@@ -846,7 +846,7 @@
|
|
|
846
846
|
"text": "4.7.0"
|
|
847
847
|
}
|
|
848
848
|
],
|
|
849
|
-
"docs": "Assign this device to a specific update channel at runtime.\n\nChannels allow you to distribute different bundle versions to different groups of users\n(e.g., \"production\", \"beta\", \"staging\"). This method switches the device to a new channel.\n\n**Requirements:**\n- The target channel must allow self-assignment (configured in your Capgo dashboard or backend)\n- The backend may accept or reject the request based on channel settings\n\n**When to use:**\n- After the app is ready and the user has interacted (e.g., opted into beta program)\n- To implement in-app channel switching (beta toggle, tester access, etc.)\n- For user-driven channel changes\n\n**When NOT to use:**\n- At app boot/initialization - use {@link PluginsConfig.CapacitorUpdater.defaultChannel} config instead\n- Before user interaction\n\n**Important: Listen for the `channelPrivate` event**\n\nWhen a user attempts to set a channel that doesn't allow device self-assignment, the method will\nthrow an error AND fire a {@link addListener}('channelPrivate') event. You should listen to this event\nto provide appropriate feedback to users:\n\n```typescript\nCapacitorUpdater.addListener('channelPrivate', (data) => {\n console.warn(`Cannot access channel \"${data.channel}\": ${data.message}`);\n // Show user-friendly message\n});\n```\n\nThis sends a request to the Capgo backend
|
|
849
|
+
"docs": "Assign this device to a specific update channel at runtime.\n\nChannels allow you to distribute different bundle versions to different groups of users\n(e.g., \"production\", \"beta\", \"staging\"). This method switches the device to a new channel.\n\n**Device Override UI:** `setChannel()` validates the channel with the backend, then stores the\nselected channel locally on the device. It does not create or update a backend Device Override,\nso the device will not appear as overridden in the Capgo dashboard. Only assignments created\nfrom the dashboard or the Public API are shown in the Device Override UI.\n\n**Requirements:**\n- The target channel must allow self-assignment (configured in your Capgo dashboard or backend)\n- The backend may accept or reject the request based on channel settings\n\n**When to use:**\n- After the app is ready and the user has interacted (e.g., opted into beta program)\n- To implement in-app channel switching (beta toggle, tester access, etc.)\n- For user-driven channel changes\n\n**When NOT to use:**\n- At app boot/initialization - use {@link PluginsConfig.CapacitorUpdater.defaultChannel} config instead\n- Before user interaction\n\n**Important: Listen for the `channelPrivate` event**\n\nWhen a user attempts to set a channel that doesn't allow device self-assignment, the method will\nthrow an error AND fire a {@link addListener}('channelPrivate') event. You should listen to this event\nto provide appropriate feedback to users:\n\n```typescript\nCapacitorUpdater.addListener('channelPrivate', (data) => {\n console.warn(`Cannot access channel \"${data.channel}\": ${data.message}`);\n // Show user-friendly message\n});\n```\n\nThis sends a request to the Capgo backend to validate the specified channel, then stores the\nchannel locally on the device.",
|
|
850
850
|
"complexTypes": [
|
|
851
851
|
"ChannelRes",
|
|
852
852
|
"SetChannelOptions"
|
|
@@ -886,7 +886,7 @@
|
|
|
886
886
|
"text": "4.7.0"
|
|
887
887
|
}
|
|
888
888
|
],
|
|
889
|
-
"docs": "Remove the
|
|
889
|
+
"docs": "Remove the plugin-managed local channel assignment and return to the default channel.\n\nThis clears only the channel stored locally by {@link setChannel}; it does not delete Dashboard or Public API Device Override records. After the local assignment is cleared, normal channel precedence applies:\n- An existing Dashboard or Public API Device Override, if one exists\n- The {@link PluginsConfig.CapacitorUpdater.defaultChannel} if configured, or\n- Your backend default channel for this app\n\nUse this when:\n- Users opt out of beta/testing programs\n- You want to reset a device to standard update distribution\n- Testing channel switching behavior",
|
|
890
890
|
"complexTypes": [
|
|
891
891
|
"UnsetChannelOptions"
|
|
892
892
|
],
|
|
@@ -1013,7 +1013,7 @@
|
|
|
1013
1013
|
"text": "{Error} If the operation fails."
|
|
1014
1014
|
}
|
|
1015
1015
|
],
|
|
1016
|
-
"docs": "Get the unique, privacy-friendly identifier for this device.\n\nThis ID is used to identify the device when communicating with update servers.\nIt's automatically generated and stored securely by the plugin.\n\n**Privacy & Security characteristics:**\n- Generated as a UUID (not based on hardware identifiers)\n- Stored securely in platform-specific secure storage\n- Android:
|
|
1016
|
+
"docs": "Get the unique, privacy-friendly identifier for this device.\n\nThis ID is used to identify the device when communicating with update servers.\nIt's automatically generated and stored securely by the plugin.\n\n**Privacy & Security characteristics:**\n- Generated as a UUID (not based on hardware identifiers)\n- Stored securely in platform-specific secure storage\n- Android: mirrored into backup-restorable app preferences for reinstall restore\n- iOS: Keychain with `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`\n- Not synced to cloud (iOS)\n- Follows Apple and Google privacy best practices\n- Users can clear it via system settings (Android) or keychain access (iOS)\n\n**Persistence:**\nThe device ID persists across app reinstalls to maintain consistent device identity\nfor update tracking and analytics when platform storage is preserved. On Android,\napps with custom backup rules must keep the plugin app preferences eligible for\nbackup/restore; disabling Android backup or clearing app data creates a new ID.\n\nUse this to:\n- Debug update delivery issues (check what ID the server sees)\n- Implement device-specific features\n- Correlate server logs with specific devices",
|
|
1017
1017
|
"complexTypes": [
|
|
1018
1018
|
"DeviceId"
|
|
1019
1019
|
],
|
|
@@ -938,6 +938,11 @@ export interface CapacitorUpdaterPlugin {
|
|
|
938
938
|
* Channels allow you to distribute different bundle versions to different groups of users
|
|
939
939
|
* (e.g., "production", "beta", "staging"). This method switches the device to a new channel.
|
|
940
940
|
*
|
|
941
|
+
* **Device Override UI:** `setChannel()` validates the channel with the backend, then stores the
|
|
942
|
+
* selected channel locally on the device. It does not create or update a backend Device Override,
|
|
943
|
+
* so the device will not appear as overridden in the Capgo dashboard. Only assignments created
|
|
944
|
+
* from the dashboard or the Public API are shown in the Device Override UI.
|
|
945
|
+
*
|
|
941
946
|
* **Requirements:**
|
|
942
947
|
* - The target channel must allow self-assignment (configured in your Capgo dashboard or backend)
|
|
943
948
|
* - The backend may accept or reject the request based on channel settings
|
|
@@ -964,7 +969,8 @@ export interface CapacitorUpdaterPlugin {
|
|
|
964
969
|
* });
|
|
965
970
|
* ```
|
|
966
971
|
*
|
|
967
|
-
* This sends a request to the Capgo backend
|
|
972
|
+
* This sends a request to the Capgo backend to validate the specified channel, then stores the
|
|
973
|
+
* channel locally on the device.
|
|
968
974
|
*
|
|
969
975
|
* @param options The {@link SetChannelOptions} containing the channel name and optional auto-update trigger.
|
|
970
976
|
* @returns {Promise<ChannelRes>} Channel operation result with status and optional error/message.
|
|
@@ -973,11 +979,12 @@ export interface CapacitorUpdaterPlugin {
|
|
|
973
979
|
*/
|
|
974
980
|
setChannel(options: SetChannelOptions): Promise<ChannelRes>;
|
|
975
981
|
/**
|
|
976
|
-
* Remove the
|
|
982
|
+
* Remove the plugin-managed local channel assignment and return to the default channel.
|
|
977
983
|
*
|
|
978
|
-
* This
|
|
984
|
+
* This clears only the channel stored locally by {@link setChannel}; it does not delete Dashboard or Public API Device Override records. After the local assignment is cleared, normal channel precedence applies:
|
|
985
|
+
* - An existing Dashboard or Public API Device Override, if one exists
|
|
979
986
|
* - The {@link PluginsConfig.CapacitorUpdater.defaultChannel} if configured, or
|
|
980
|
-
* - Your backend
|
|
987
|
+
* - Your backend default channel for this app
|
|
981
988
|
*
|
|
982
989
|
* Use this when:
|
|
983
990
|
* - Users opt out of beta/testing programs
|
|
@@ -1088,7 +1095,7 @@ export interface CapacitorUpdaterPlugin {
|
|
|
1088
1095
|
* **Privacy & Security characteristics:**
|
|
1089
1096
|
* - Generated as a UUID (not based on hardware identifiers)
|
|
1090
1097
|
* - Stored securely in platform-specific secure storage
|
|
1091
|
-
* - Android:
|
|
1098
|
+
* - Android: mirrored into backup-restorable app preferences for reinstall restore
|
|
1092
1099
|
* - iOS: Keychain with `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`
|
|
1093
1100
|
* - Not synced to cloud (iOS)
|
|
1094
1101
|
* - Follows Apple and Google privacy best practices
|
|
@@ -1096,7 +1103,9 @@ export interface CapacitorUpdaterPlugin {
|
|
|
1096
1103
|
*
|
|
1097
1104
|
* **Persistence:**
|
|
1098
1105
|
* The device ID persists across app reinstalls to maintain consistent device identity
|
|
1099
|
-
* for update tracking and analytics.
|
|
1106
|
+
* for update tracking and analytics when platform storage is preserved. On Android,
|
|
1107
|
+
* apps with custom backup rules must keep the plugin app preferences eligible for
|
|
1108
|
+
* backup/restore; disabling Android backup or clearing app data creates a new ID.
|
|
1100
1109
|
*
|
|
1101
1110
|
* Use this to:
|
|
1102
1111
|
* - Debug update delivery issues (check what ID the server sees)
|