react-native-update 10.46.0 → 10.47.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "10.46.0",
3
+ "version": "10.47.0",
4
4
  "description": "react-native hot update",
5
5
  "main": "src/index",
6
6
  "types": "src/index.ts",
package/src/client.ts CHANGED
@@ -41,6 +41,11 @@ import {
41
41
  import i18n from './i18n';
42
42
  import { toUpdateError, UpdateError, UpdateErrorCode } from './error';
43
43
  import { dedupeEndpoints, executeEndpointFallback } from './endpoint';
44
+ import {
45
+ resolveServerEventHash,
46
+ resolveServerEventType,
47
+ truncateDetail,
48
+ } from './telemetry';
44
49
 
45
50
  /**
46
51
  * Receives every error the client reports, alongside the report event type.
@@ -130,6 +135,9 @@ export class Pushy {
130
135
  clientType: 'Pushy' | 'Cresc' = 'Pushy';
131
136
  lastChecking?: number;
132
137
  lastRespJson?: Promise<CheckResult>;
138
+ // Endpoint that most recently served a successful checkUpdate; telemetry
139
+ // reuses it instead of re-running the fallback race.
140
+ private lastWorkingEndpoint?: string;
133
141
 
134
142
  version = cInfo.rnu;
135
143
  loggerPromise = (() => {
@@ -207,6 +215,8 @@ export class Pushy {
207
215
  data?: Record<string, string | number>;
208
216
  }) => {
209
217
  log(`${type} ${code ? `[${code}] ` : ''}${message}`);
218
+ // Fire-and-forget server telemetry; must not wait for the logger below.
219
+ this.reportToServer({ type, message, code, data });
210
220
  if (this.options.logger === noop) {
211
221
  // Wait briefly for a logger to arrive via setOptions (e.g. the rollback
212
222
  // report fires in the constructor before the user configures one), but
@@ -242,6 +252,72 @@ export class Pushy {
242
252
  log('logger error:', e?.message || e);
243
253
  }
244
254
  };
255
+ /**
256
+ * Best-effort lifecycle event reporting to the update server (aggregate
257
+ * counts + sampled failure details power the version health view and the
258
+ * rollback safety net server-side). Single POST to the last known working
259
+ * endpoint, no retry, no fallback race; any failure is swallowed — telemetry
260
+ * must never affect the update flow. Opt out with disableTelemetry.
261
+ */
262
+ private reportToServer = ({
263
+ type,
264
+ message = '',
265
+ code,
266
+ data = {},
267
+ }: {
268
+ type: EventType;
269
+ message?: string;
270
+ code?: UpdateErrorCode;
271
+ data?: Record<string, string | number>;
272
+ }) => {
273
+ try {
274
+ if (__DEV__ || this.options.disableTelemetry) {
275
+ return;
276
+ }
277
+ const serverType = resolveServerEventType(type, code);
278
+ if (!serverType) {
279
+ return;
280
+ }
281
+ const { appKey } = this.options;
282
+ const endpoint =
283
+ this.lastWorkingEndpoint || this.options.server?.main?.[0];
284
+ if (!appKey || !endpoint) {
285
+ return;
286
+ }
287
+ const hash = resolveServerEventHash({ serverType, data, currentVersion });
288
+ if (!hash) {
289
+ return;
290
+ }
291
+ const send = (payloadType: typeof serverType, detail?: string) =>
292
+ fetchWithTimeout(
293
+ `${endpoint}/report/${appKey}`,
294
+ {
295
+ method: 'POST',
296
+ headers: { 'Content-Type': 'application/json' },
297
+ body: JSON.stringify({
298
+ type: payloadType,
299
+ hash,
300
+ packageVersion:
301
+ this.options.overridePackageVersion || packageVersion,
302
+ cInfo,
303
+ detail: truncateDetail(detail),
304
+ }),
305
+ },
306
+ DEFAULT_FETCH_TIMEOUT_MS,
307
+ ).catch((e: any) => {
308
+ log('telemetry report failed:', e?.message || e);
309
+ });
310
+ send(serverType, message || undefined);
311
+ // A download that only succeeded after an incremental patch failed is
312
+ // still a patch_fail signal server-side (diff quality), carried in
313
+ // data.error alongside the downloadSuccess event.
314
+ if (serverType === 'download_success' && data.error) {
315
+ send('patch_fail', String(data.error));
316
+ }
317
+ } catch (e: any) {
318
+ log('telemetry error:', e?.message || e);
319
+ }
320
+ };
245
321
  throwIfEnabled = (e: Error) => {
246
322
  if (this.options.throwError) {
247
323
  throw e;
@@ -387,6 +463,7 @@ export class Pushy {
387
463
  });
388
464
 
389
465
  log('check endpoint success', endpoint);
466
+ this.lastWorkingEndpoint = endpoint;
390
467
  return value;
391
468
  };
392
469
  assertDebug = (matter: string) => {
@@ -0,0 +1,83 @@
1
+ import type { UpdateErrorCode } from './error';
2
+ import type { EventType } from './type';
3
+
4
+ // Server-side lifecycle event intake (POST {endpoint}/report/{appKey}); the
5
+ // enum must stay in sync with the server's client_event_type. Only these five
6
+ // aggregate types exist server-side — everything else stays local-only.
7
+ export type ServerEventType =
8
+ | 'download_success'
9
+ | 'download_fail'
10
+ | 'patch_fail'
11
+ | 'rollback'
12
+ | 'mark_success';
13
+
14
+ // Failure detail is stored server-side in a VARCHAR(512); stay under it.
15
+ export const MAX_DETAIL_LENGTH = 500;
16
+
17
+ export interface ServerReportPayload {
18
+ type: ServerEventType;
19
+ hash: string;
20
+ packageVersion?: string;
21
+ cInfo?: {
22
+ rnu?: string;
23
+ rn?: string;
24
+ os?: string;
25
+ uuid?: string;
26
+ };
27
+ detail?: string;
28
+ }
29
+
30
+ /**
31
+ * Maps a local report event to the server-side aggregate type, or undefined
32
+ * for events the server does not collect. A DOWNLOAD_FAILED whose underlying
33
+ * native code was PATCH_FAILED counts as patch_fail (the download itself
34
+ * worked; applying it did not), same for switch-version failures.
35
+ */
36
+ export const resolveServerEventType = (
37
+ type: EventType,
38
+ code?: UpdateErrorCode,
39
+ ): ServerEventType | undefined => {
40
+ switch (type) {
41
+ case 'downloadSuccess':
42
+ return 'download_success';
43
+ case 'errorUpdate':
44
+ return code === 'PATCH_FAILED' ? 'patch_fail' : 'download_fail';
45
+ case 'errorSwitchVersion':
46
+ return 'patch_fail';
47
+ case 'markSuccess':
48
+ return 'mark_success';
49
+ case 'rollback':
50
+ return 'rollback';
51
+ default:
52
+ return undefined;
53
+ }
54
+ };
55
+
56
+ /**
57
+ * The version hash a server event is about. Server-side rows key on it, so an
58
+ * event without one is not reportable (returns '').
59
+ */
60
+ export const resolveServerEventHash = ({
61
+ serverType,
62
+ data,
63
+ currentVersion,
64
+ }: {
65
+ serverType: ServerEventType;
66
+ data: Record<string, string | number>;
67
+ currentVersion: string;
68
+ }): string => {
69
+ if (serverType === 'rollback') {
70
+ return String(data.rolledBackVersion || '');
71
+ }
72
+ if (serverType === 'mark_success') {
73
+ return String(data.newVersion || currentVersion || '');
74
+ }
75
+ return String(data.newVersion || '');
76
+ };
77
+
78
+ export const truncateDetail = (detail?: string) => {
79
+ if (!detail) {
80
+ return undefined;
81
+ }
82
+ return detail.slice(0, MAX_DETAIL_LENGTH);
83
+ };
package/src/type.ts CHANGED
@@ -129,6 +129,12 @@ export interface ClientOptions {
129
129
  overridePackageVersion?: string;
130
130
  /** Maximum number of retry attempts for failed downloads (default: 3) */
131
131
  maxRetries?: number;
132
+ /**
133
+ * Disable reporting update lifecycle events (download/patch failures,
134
+ * rollback, mark success) to the update server. These aggregate stats power
135
+ * the version health view in the console. Default: false (enabled).
136
+ */
137
+ disableTelemetry?: boolean;
132
138
  }
133
139
 
134
140
  export interface UpdateTestPayload {