ubriot-updates-rn 0.1.0 → 0.1.4

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.
@@ -23,6 +23,7 @@ class UbriotUpdateState(
23
23
  var pending: UbriotUpdateEntry? = null,
24
24
  var lastKnownGood: UbriotUpdateEntry? = null,
25
25
  var launchCount: Int = 0,
26
+ var revertedFromId: String? = null,
26
27
  )
27
28
 
28
29
  class UbriotUpdatesModule(
@@ -124,6 +125,18 @@ class UbriotUpdatesModule(
124
125
  }
125
126
  }
126
127
 
128
+ @ReactMethod
129
+ fun clearRevertedFrom(promise: Promise) {
130
+ try {
131
+ val state = readState(filesDir())
132
+ state.revertedFromId = null
133
+ writeState(filesDir(), state)
134
+ promise.resolve(null)
135
+ } catch (error: Throwable) {
136
+ promise.reject("ubriot_updates_clear_reverted_failed", error)
137
+ }
138
+ }
139
+
127
140
  @ReactMethod
128
141
  fun reload(promise: Promise) {
129
142
  try {
@@ -150,6 +163,7 @@ class UbriotUpdatesModule(
150
163
  map.putMap("pending", entryToWritableMap(state.pending))
151
164
  map.putMap("lastKnownGood", entryToWritableMap(state.lastKnownGood))
152
165
  map.putInt("launchCount", state.launchCount)
166
+ map.putString("revertedFromId", state.revertedFromId)
153
167
  return map
154
168
  }
155
169
 
@@ -200,6 +214,7 @@ class UbriotUpdatesModule(
200
214
  pending = entryFromJson(json.optJSONObject("pending")),
201
215
  lastKnownGood = entryFromJson(json.optJSONObject("lastKnownGood")),
202
216
  launchCount = json.optInt("launchCount", 0),
217
+ revertedFromId = json.optString("revertedFromId", "").ifEmpty { null },
203
218
  )
204
219
  } catch (_: Throwable) {
205
220
  UbriotUpdateState()
@@ -218,6 +233,7 @@ class UbriotUpdatesModule(
218
233
  .put("pending", entryToJson(state.pending))
219
234
  .put("lastKnownGood", entryToJson(state.lastKnownGood))
220
235
  .put("launchCount", state.launchCount)
236
+ .put("revertedFromId", state.revertedFromId ?: JSONObject.NULL)
221
237
  stateFile(filesDir).writeText(json.toString())
222
238
  } catch (_: Throwable) {
223
239
  }
@@ -241,6 +257,7 @@ class UbriotUpdatesModule(
241
257
  writeState(filesDir, state)
242
258
  return active.bundlePath
243
259
  }
260
+ state.revertedFromId = active.id
244
261
  state.active = null
245
262
  state.verified = false
246
263
  state.launchCount = 0
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export type ApplyOptions = {
15
15
  restart?: boolean;
16
16
  };
17
17
  export type SyncResult = "up-to-date" | "updated";
18
+ export type LaunchOutcome = "launched" | "crashed";
18
19
  export declare function configure(next: UbriotUpdatesConfig): void;
19
20
  export declare function checkForUpdate(): Promise<UpdateCheckResult>;
20
21
  export declare function downloadUpdate(): Promise<DownloadResult>;
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ exports.sync = sync;
8
8
  exports.markLaunchSuccessful = markLaunchSuccessful;
9
9
  const react_native_1 = require("react-native");
10
10
  const MANIFEST_PATH = "/ubriot/updates/manifest";
11
+ const LAUNCH_PATH = "/ubriot/updates/launch";
11
12
  let config = null;
12
13
  let stagedManifest = null;
13
14
  function getNativeModule() {
@@ -32,6 +33,60 @@ function normalizeBase(apiBase) {
32
33
  async function verifyManifestSignature(_manifest) {
33
34
  return true;
34
35
  }
36
+ function reportLaunch(outcome, updateId) {
37
+ try {
38
+ if (!config)
39
+ return;
40
+ if (!updateId)
41
+ return;
42
+ const cfg = config;
43
+ const payload = JSON.stringify({
44
+ appId: cfg.appSlug,
45
+ channel: cfg.channel,
46
+ updateId,
47
+ platform: currentPlatform(),
48
+ outcome,
49
+ });
50
+ void fetch(`${cfg.apiBase}${LAUNCH_PATH}`, {
51
+ method: "POST",
52
+ headers: { "content-type": "application/json" },
53
+ body: payload,
54
+ }).catch(() => { });
55
+ }
56
+ catch { }
57
+ }
58
+ function reportRevertIfCrashed() {
59
+ try {
60
+ const native = react_native_1.NativeModules.UbriotUpdates;
61
+ if (!native || typeof native.getState !== "function")
62
+ return;
63
+ native
64
+ .getState()
65
+ .then((state) => {
66
+ var _a, _b, _c, _d, _e, _f;
67
+ try {
68
+ if (!state)
69
+ return;
70
+ const revertedFromId = (_c = (_b = (_a = state.revertedFromId) !== null && _a !== void 0 ? _a : state.failedUpdateId) !== null && _b !== void 0 ? _b : state.lastRevertedId) !== null && _c !== void 0 ? _c : null;
71
+ if (!revertedFromId)
72
+ return;
73
+ const survivingId = (_f = (_d = state.runningId) !== null && _d !== void 0 ? _d : (_e = state.active) === null || _e === void 0 ? void 0 : _e.id) !== null && _f !== void 0 ? _f : null;
74
+ if (revertedFromId === survivingId)
75
+ return;
76
+ reportLaunch("crashed", revertedFromId);
77
+ try {
78
+ if (typeof native.clearRevertedFrom === "function") {
79
+ native.clearRevertedFrom().catch(() => { });
80
+ }
81
+ }
82
+ catch { }
83
+ }
84
+ catch { }
85
+ })
86
+ .catch(() => { });
87
+ }
88
+ catch { }
89
+ }
35
90
  function configure(next) {
36
91
  config = {
37
92
  apiBase: normalizeBase(next.apiBase),
@@ -39,6 +94,7 @@ function configure(next) {
39
94
  channel: next.channel,
40
95
  runtimeVersion: next.runtimeVersion,
41
96
  };
97
+ reportRevertIfCrashed();
42
98
  }
43
99
  async function checkForUpdate() {
44
100
  var _a, _b, _c, _d;
@@ -150,6 +206,10 @@ function markLaunchSuccessful() {
150
206
  const native = react_native_1.NativeModules.UbriotUpdates;
151
207
  if (native && typeof native.markLaunchSuccessful === "function") {
152
208
  native.markLaunchSuccessful().catch(() => { });
209
+ native
210
+ .getState()
211
+ .then((state) => { var _a, _b, _c; return reportLaunch("launched", (_c = (_a = state === null || state === void 0 ? void 0 : state.runningId) !== null && _a !== void 0 ? _a : (_b = state === null || state === void 0 ? void 0 : state.active) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : null); })
212
+ .catch(() => { });
153
213
  }
154
214
  }
155
215
  catch {
@@ -36,6 +36,7 @@ RCT_EXPORT_MODULE();
36
36
  @"pending": [NSNull null],
37
37
  @"lastKnownGood": [NSNull null],
38
38
  @"launchCount": @0,
39
+ @"revertedFromId": [NSNull null],
39
40
  } mutableCopy];
40
41
  }
41
42
 
@@ -94,6 +95,7 @@ RCT_EXPORT_MODULE();
94
95
  [self writeState:state];
95
96
  return [NSURL fileURLWithPath:path];
96
97
  }
98
+ state[@"revertedFromId"] = activeId ?: [NSNull null];
97
99
  state[@"active"] = [NSNull null];
98
100
  state[@"verified"] = @NO;
99
101
  state[@"launchCount"] = @0;
@@ -214,6 +216,17 @@ RCT_EXPORT_METHOD(markLaunchSuccessful:(RCTPromiseResolveBlock)resolve rejecter:
214
216
  }
215
217
  }
216
218
 
219
+ RCT_EXPORT_METHOD(clearRevertedFrom:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
220
+ @try {
221
+ NSMutableDictionary *state = [UbriotUpdates readState];
222
+ state[@"revertedFromId"] = [NSNull null];
223
+ [UbriotUpdates writeState:state];
224
+ resolve(nil);
225
+ } @catch (NSException *exception) {
226
+ reject(@"ubriot_updates_clear_reverted_failed", exception.reason, nil);
227
+ }
228
+ }
229
+
217
230
  RCT_EXPORT_METHOD(reload:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
218
231
  dispatch_async(dispatch_get_main_queue(), ^{
219
232
  @try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ubriot-updates-rn",
3
- "version": "0.1.0",
3
+ "version": "0.1.4",
4
4
  "description": "Ubriot over-the-air JS update client for bare React Native (iOS + Android)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
package/src/index.ts CHANGED
@@ -34,14 +34,20 @@ type NativeState = {
34
34
  pending: NativeEntry;
35
35
  lastKnownGood: NativeEntry;
36
36
  launchCount: number;
37
+ revertedFromId?: string | null;
38
+ failedUpdateId?: string | null;
39
+ lastRevertedId?: string | null;
37
40
  };
38
41
 
42
+ export type LaunchOutcome = "launched" | "crashed";
43
+
39
44
  type NativeUbriotUpdates = {
40
45
  getState(): Promise<NativeState>;
41
46
  downloadBundle(url: string, manifestId: string): Promise<{ ready: boolean; bundlePath: string }>;
42
47
  applyPending(): Promise<void>;
43
48
  markLaunchSuccessful(): Promise<void>;
44
49
  reload(): Promise<void>;
50
+ clearRevertedFrom?(): Promise<void>;
45
51
  };
46
52
 
47
53
  type LaunchManifest = {
@@ -50,6 +56,7 @@ type LaunchManifest = {
50
56
  };
51
57
 
52
58
  const MANIFEST_PATH = "/ubriot/updates/manifest";
59
+ const LAUNCH_PATH = "/ubriot/updates/launch";
53
60
 
54
61
  let config: UbriotUpdatesConfig | null = null;
55
62
  let stagedManifest: LaunchManifest | null = null;
@@ -81,6 +88,52 @@ async function verifyManifestSignature(_manifest: unknown): Promise<boolean> {
81
88
  return true;
82
89
  }
83
90
 
91
+ function reportLaunch(outcome: LaunchOutcome, updateId: string | null | undefined): void {
92
+ try {
93
+ if (!config) return;
94
+ if (!updateId) return;
95
+ const cfg = config;
96
+ const payload = JSON.stringify({
97
+ appId: cfg.appSlug,
98
+ channel: cfg.channel,
99
+ updateId,
100
+ platform: currentPlatform(),
101
+ outcome,
102
+ });
103
+ void fetch(`${cfg.apiBase}${LAUNCH_PATH}`, {
104
+ method: "POST",
105
+ headers: { "content-type": "application/json" },
106
+ body: payload,
107
+ }).catch(() => {});
108
+ } catch {}
109
+ }
110
+
111
+ function reportRevertIfCrashed(): void {
112
+ try {
113
+ const native = NativeModules.UbriotUpdates as NativeUbriotUpdates | undefined;
114
+ if (!native || typeof native.getState !== "function") return;
115
+ native
116
+ .getState()
117
+ .then((state) => {
118
+ try {
119
+ if (!state) return;
120
+ const revertedFromId =
121
+ state.revertedFromId ?? state.failedUpdateId ?? state.lastRevertedId ?? null;
122
+ if (!revertedFromId) return;
123
+ const survivingId = state.runningId ?? state.active?.id ?? null;
124
+ if (revertedFromId === survivingId) return;
125
+ reportLaunch("crashed", revertedFromId);
126
+ try {
127
+ if (typeof native.clearRevertedFrom === "function") {
128
+ native.clearRevertedFrom().catch(() => {});
129
+ }
130
+ } catch {}
131
+ } catch {}
132
+ })
133
+ .catch(() => {});
134
+ } catch {}
135
+ }
136
+
84
137
  export function configure(next: UbriotUpdatesConfig): void {
85
138
  config = {
86
139
  apiBase: normalizeBase(next.apiBase),
@@ -88,6 +141,7 @@ export function configure(next: UbriotUpdatesConfig): void {
88
141
  channel: next.channel,
89
142
  runtimeVersion: next.runtimeVersion,
90
143
  };
144
+ reportRevertIfCrashed();
91
145
  }
92
146
 
93
147
  export async function checkForUpdate(): Promise<UpdateCheckResult> {
@@ -211,6 +265,10 @@ export function markLaunchSuccessful(): void {
211
265
  const native = NativeModules.UbriotUpdates as NativeUbriotUpdates | undefined;
212
266
  if (native && typeof native.markLaunchSuccessful === "function") {
213
267
  native.markLaunchSuccessful().catch(() => {});
268
+ native
269
+ .getState()
270
+ .then((state) => reportLaunch("launched", state?.runningId ?? state?.active?.id ?? null))
271
+ .catch(() => {});
214
272
  }
215
273
  } catch {
216
274
  // never allow launch bookkeeping to crash the host app