react-native-update 10.50.0 → 10.51.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.
Files changed (49) hide show
  1. package/NATIVE_CHECKUPDATE_DESIGN.md +251 -21
  2. package/NATIVE_CHECK_FOLLOWUPS.md +365 -0
  3. package/README-CN.md +14 -1
  4. package/README.md +14 -1
  5. package/android/jni/Android.mk +5 -1
  6. package/android/lib/arm64-v8a/librnupdate.so +0 -0
  7. package/android/lib/armeabi-v7a/librnupdate.so +0 -0
  8. package/android/lib/x86/librnupdate.so +0 -0
  9. package/android/lib/x86_64/librnupdate.so +0 -0
  10. package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +76 -21
  11. package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -0
  12. package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +455 -0
  13. package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java +23 -0
  14. package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +160 -42
  15. package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +44 -0
  16. package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
  17. package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
  18. package/cpp/update_flow_core/flow_json.cpp +522 -0
  19. package/cpp/update_flow_core/flow_json.h +103 -0
  20. package/cpp/update_flow_core/tests/flow_vectors.json +1401 -0
  21. package/cpp/update_flow_core/tests/update_flow_core_test.cpp +317 -0
  22. package/cpp/update_flow_core/update_flow_core.cpp +301 -0
  23. package/cpp/update_flow_core/update_flow_core.h +77 -0
  24. package/cpp/update_flow_core/update_flow_jni.cpp +121 -0
  25. package/harmony/pushy/src/main/cpp/CMakeLists.txt +7 -0
  26. package/harmony/pushy/src/main/cpp/pushy.cpp +103 -1
  27. package/harmony/pushy/src/main/ets/DownloadTask.ts +48 -9
  28. package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +3 -0
  29. package/harmony/pushy/src/main/ets/MonotonicClock.ts +13 -0
  30. package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +500 -0
  31. package/harmony/pushy/src/main/ets/NativePatchCore.ts +14 -0
  32. package/harmony/pushy/src/main/ets/PathUtils.ts +21 -0
  33. package/harmony/pushy/src/main/ets/PushyTurboModule.ts +22 -0
  34. package/harmony/pushy/src/main/ets/UpdateContext.ts +148 -69
  35. package/harmony/pushy.har +0 -0
  36. package/ios/RCTPushy/RCTPushy.mm +779 -61
  37. package/ios/RCTPushy/RCTPushyDownloader.h +1 -0
  38. package/ios/RCTPushy/RCTPushyDownloader.mm +4 -3
  39. package/package.json +3 -1
  40. package/react-native-update.podspec +2 -0
  41. package/src/NativePushy.ts +14 -0
  42. package/src/client.ts +341 -118
  43. package/src/endpoint.ts +7 -29
  44. package/src/provider.tsx +38 -2
  45. package/src/type.ts +26 -0
  46. package/src/updateFlowCore.ts +320 -0
  47. package/src/utils.ts +1 -12
  48. package/src/isInRollout.ts +0 -87
  49. package/src/resolveCheckResult.ts +0 -30
package/src/endpoint.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import { UpdateError } from './error';
2
+ import { dedupeEndpoints, orderEndpointCandidates } from './updateFlowCore';
3
+
4
+ export { dedupeEndpoints };
2
5
 
3
6
  export interface EndpointAttemptSuccess<T> {
4
7
  endpoint: string;
@@ -28,33 +31,6 @@ const normalizeError = (error: unknown) => {
28
31
  return new Error(String(error));
29
32
  };
30
33
 
31
- export const dedupeEndpoints = (
32
- endpoints: Array<string | null | undefined>
33
- ): string[] => {
34
- const result: string[] = [];
35
- const visited = new Set<string>();
36
-
37
- for (const endpoint of endpoints) {
38
- if (!endpoint || visited.has(endpoint)) {
39
- continue;
40
- }
41
- visited.add(endpoint);
42
- result.push(endpoint);
43
- }
44
-
45
- return result;
46
- };
47
-
48
- export const pickRandomEndpoint = (
49
- endpoints: string[],
50
- random: () => number = Math.random
51
- ) => {
52
- if (!endpoints.length) {
53
- throw new UpdateError('No endpoints configured', 'NO_ENDPOINTS');
54
- }
55
- return endpoints[Math.floor(random() * endpoints.length)];
56
- };
57
-
58
34
  export const DEFAULT_HEDGE_DELAY_MS = 250;
59
35
 
60
36
  /**
@@ -165,13 +141,15 @@ export async function executeEndpointFallback<T>({
165
141
  onFirstFailure,
166
142
  }: ExecuteEndpointFallbackOptions<T>): Promise<EndpointAttemptSuccess<T>> {
167
143
  const excludedEndpoints = new Set<string>();
168
- let candidates = dedupeEndpoints(configuredEndpoints);
144
+ // The candidate ordering (random first pick, configured order as fallback)
145
+ // is pure policy; this side only executes it.
146
+ let candidates = orderEndpointCandidates(configuredEndpoints, random());
169
147
 
170
148
  if (!candidates.length) {
171
149
  throw new UpdateError('No endpoints configured', 'NO_ENDPOINTS');
172
150
  }
173
151
 
174
- const firstEndpoint = pickRandomEndpoint(candidates, random);
152
+ const firstEndpoint = candidates[0];
175
153
 
176
154
  try {
177
155
  return {
package/src/provider.tsx CHANGED
@@ -18,13 +18,15 @@ import { URL } from 'react-native-url-polyfill';
18
18
  import { type Cresc, type Pushy, sharedState } from './client';
19
19
  import { ProgressContext, UpdateContext } from './context';
20
20
  import {
21
+ cInfo,
21
22
  currentVersion,
22
23
  currentVersionInfo,
23
24
  getCurrentVersionInfo,
24
25
  packageVersion,
26
+ rolledBackVersion,
25
27
  } from './core';
26
- import { resolveCheckResult } from './resolveCheckResult';
27
28
  import type { CheckResult, ProgressData, UpdateTestPayload } from './type';
29
+ import { decideDownload, resolveCheckResult } from './updateFlowCore';
28
30
  import { assertWeb, log, noop } from './utils';
29
31
 
30
32
  export const UpdateProvider = ({
@@ -222,7 +224,41 @@ export const UpdateProvider = ({
222
224
  // known updateInfo instead of overwriting it with an empty object.
223
225
  return;
224
226
  }
225
- const info = resolveCheckResult(rootInfo);
227
+ let info = resolveCheckResult(
228
+ rootInfo,
229
+ {
230
+ packageVersion: client.getEffectivePackageVersion(),
231
+ currentVersion,
232
+ uuid: cInfo.uuid,
233
+ },
234
+ log
235
+ );
236
+ if (
237
+ !info.expired &&
238
+ info.update &&
239
+ (typeof info.hash !== 'string' || info.hash.length === 0)
240
+ ) {
241
+ // A malformed rollout/root entry must not produce an alert whose
242
+ // confirm button can never download anything. Surface it to the
243
+ // developer telemetry/logger and present it to the app as no update.
244
+ client.reportInvalidUpdateOnce('missingHash');
245
+ info = { upToDate: true };
246
+ }
247
+ if (info.update && !info.expired) {
248
+ const decision = decideDownload(
249
+ info,
250
+ { currentVersion, rolledBackVersion },
251
+ __DEV__
252
+ );
253
+ if (decision.action === 'none') {
254
+ if (decision.reason === 'noArtifact') {
255
+ // Invalid server data is worth reporting; local rollout guards are
256
+ // expected no-ops and stay silent.
257
+ client.reportInvalidUpdateOnce('noArtifact', info.hash || '');
258
+ }
259
+ info = { upToDate: true };
260
+ }
261
+ }
226
262
  if (info.update) {
227
263
  info.description = info.description ?? '';
228
264
  }
package/src/type.ts CHANGED
@@ -9,6 +9,14 @@ export interface VersionInfo {
9
9
  rollout: {
10
10
  [packageVersion: string]: number;
11
11
  };
12
+ /**
13
+ * Server-set per-version override: the native cold-start check activates
14
+ * this version for the next launch regardless of the client's
15
+ * updateStrategy (the brick-rescue directive). Native-only — the JS
16
+ * interactive flow ignores it. The device-local rolledBack guard still
17
+ * wins, and first_time crash protection still applies.
18
+ */
19
+ forceBoot?: boolean;
12
20
  [key: string]: any;
13
21
  };
14
22
  pdiff?: string;
@@ -169,6 +177,24 @@ export interface ClientOptions {
169
177
  * the version health view in the console. Default: false (enabled).
170
178
  */
171
179
  disableTelemetry?: boolean;
180
+ /**
181
+ * Disable the native cold-start update check: the background check that runs
182
+ * a few seconds after every launch, independent of JS
183
+ * (NATIVE_CHECKUPDATE_DESIGN §10). Default: false (enabled).
184
+ *
185
+ * That check is what rescues a device bricked by a bad update — its JS never
186
+ * runs, so nothing else can pull the fix. Turning it off gives up that
187
+ * recovery path (and the response cache the JS check reuses) in exchange for
188
+ * one fewer background request per cold start; choose it only when the extra
189
+ * request is itself the problem (traffic/battery budgets, privacy manifests,
190
+ * consent-gated networking).
191
+ *
192
+ * Orthogonal to `checkStrategy`, which governs activation authority rather
193
+ * than whether the check runs: with `checkStrategy: null` the native check
194
+ * still downloads but never activates on its own — only the server's
195
+ * per-version forceBoot directive may.
196
+ */
197
+ disableNativeCheck?: boolean;
172
198
  }
173
199
 
174
200
  export interface UpdateTestPayload {
@@ -0,0 +1,320 @@
1
+ import type { CheckResult } from './type';
2
+
3
+ // 更新流程的纯决策层:无 IO、不依赖 react-native、无模块级状态,所有输入均由
4
+ // 参数传入(仅允许 type-only import)。这一层必须保持可在裸 JS 引擎中求值——
5
+ // 它就是未来 guardian bundle 的编译单元(NATIVE_CHECKUPDATE_DESIGN §5),
6
+ // IO(HTTP/下载/落盘)由调用方(现在是 client.ts,将来是原生编排)执行。
7
+
8
+ /* eslint-disable no-fallthrough */
9
+ /* eslint-disable no-bitwise */
10
+ export function murmurhash3_32_gc(key: string, seed = 0) {
11
+ let remainder: number,
12
+ bytes: number,
13
+ h1: number,
14
+ h1b: number,
15
+ c1: number,
16
+ c2: number,
17
+ k1: number,
18
+ i: number;
19
+
20
+ remainder = key.length & 3; // key.length % 4
21
+ bytes = key.length - remainder;
22
+ h1 = seed;
23
+ c1 = 0xcc9e2d51;
24
+ c2 = 0x1b873593;
25
+ i = 0;
26
+
27
+ while (i < bytes) {
28
+ k1 =
29
+ (key.charCodeAt(i) & 0xff) |
30
+ ((key.charCodeAt(++i) & 0xff) << 8) |
31
+ ((key.charCodeAt(++i) & 0xff) << 16) |
32
+ ((key.charCodeAt(++i) & 0xff) << 24);
33
+ ++i;
34
+
35
+ k1 =
36
+ ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
37
+ k1 = (k1 << 15) | (k1 >>> 17);
38
+ k1 =
39
+ ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
40
+
41
+ h1 ^= k1;
42
+ h1 = (h1 << 13) | (h1 >>> 19);
43
+ h1b =
44
+ ((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) & 0xffffffff;
45
+ h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
46
+ }
47
+
48
+ k1 = 0;
49
+
50
+ switch (remainder) {
51
+ // biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough
52
+ case 3:
53
+ k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
54
+ // biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough
55
+ case 2:
56
+ k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
57
+ case 1:
58
+ k1 ^= key.charCodeAt(i) & 0xff;
59
+
60
+ k1 =
61
+ ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) &
62
+ 0xffffffff;
63
+ k1 = (k1 << 15) | (k1 >>> 17);
64
+ k1 =
65
+ ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) &
66
+ 0xffffffff;
67
+ h1 ^= k1;
68
+ }
69
+
70
+ h1 ^= key.length;
71
+
72
+ h1 ^= h1 >>> 16;
73
+ h1 =
74
+ ((h1 & 0xffff) * 0x85ebca6b +
75
+ ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) &
76
+ 0xffffffff;
77
+ h1 ^= h1 >>> 13;
78
+ h1 =
79
+ ((h1 & 0xffff) * 0xc2b2ae35 +
80
+ ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) &
81
+ 0xffffffff;
82
+ h1 ^= h1 >>> 16;
83
+
84
+ return h1 >>> 0;
85
+ }
86
+
87
+ export function isInRollout(rollout: number, uuid: string) {
88
+ return murmurhash3_32_gc(uuid) % 100 < rollout;
89
+ }
90
+
91
+ export function joinUrls(paths: string[], fileName?: string) {
92
+ if (fileName) {
93
+ return paths.map((path) => {
94
+ const normalizedPath = path.replace(/\/+$/, '');
95
+ // Keep explicit http(s) URLs for local/self-hosted update sources.
96
+ const baseUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(normalizedPath)
97
+ ? normalizedPath
98
+ : `https://${normalizedPath}`;
99
+ return `${baseUrl}/${fileName}`;
100
+ });
101
+ }
102
+ }
103
+
104
+ export const dedupeEndpoints = (
105
+ endpoints: Array<string | null | undefined>
106
+ ): string[] => {
107
+ const result: string[] = [];
108
+ const visited = new Set<string>();
109
+
110
+ for (const endpoint of endpoints) {
111
+ if (!endpoint || visited.has(endpoint)) {
112
+ continue;
113
+ }
114
+ visited.add(endpoint);
115
+ result.push(endpoint);
116
+ }
117
+
118
+ return result;
119
+ };
120
+
121
+ /**
122
+ * The endpoint plan: dedupe, then move the sampled pick to the front (load
123
+ * spreading), keeping the rest in configured order as the fallback sequence.
124
+ * `randomSample` ∈ [0, 1) is injected by the caller — this layer cannot draw
125
+ * randomness itself.
126
+ */
127
+ export function orderEndpointCandidates(
128
+ endpoints: Array<string | null | undefined>,
129
+ randomSample = 0
130
+ ): string[] {
131
+ const deduped = dedupeEndpoints(endpoints);
132
+ if (deduped.length < 2) {
133
+ return deduped;
134
+ }
135
+ const first = Math.max(
136
+ 0,
137
+ Math.min(
138
+ Number.isFinite(randomSample)
139
+ ? Math.floor(randomSample * deduped.length)
140
+ : 0,
141
+ deduped.length - 1
142
+ )
143
+ );
144
+ return [
145
+ deduped[first],
146
+ ...deduped.slice(0, first),
147
+ ...deduped.slice(first + 1),
148
+ ];
149
+ }
150
+
151
+ export interface CheckRequestInput {
152
+ /** Effective native package version (overridePackageVersion already applied). */
153
+ packageVersion: string;
154
+ /** Hash of the currently running JS version ('' when on the packaged bundle). */
155
+ currentVersion?: string;
156
+ buildTime: string;
157
+ cInfo: Record<string, string>;
158
+ supportedDiffVersion?: number;
159
+ /** '' or undefined while unknown — the field is then omitted. */
160
+ bundleHash?: string;
161
+ isDev?: boolean;
162
+ extra?: Record<string, any>;
163
+ }
164
+
165
+ export function buildCheckRequestBody({
166
+ packageVersion,
167
+ currentVersion,
168
+ buildTime,
169
+ cInfo,
170
+ supportedDiffVersion,
171
+ bundleHash,
172
+ isDev,
173
+ extra,
174
+ }: CheckRequestInput): Record<string, any> {
175
+ const body: Record<string, any> = {
176
+ packageVersion,
177
+ hash: currentVersion,
178
+ buildTime,
179
+ cInfo,
180
+ // 可消费的 diff 轨道版本(2 = hdiffv2 轨道),服务端据此门控下发
181
+ ...(supportedDiffVersion ? { diffV: supportedDiffVersion } : {}),
182
+ ...(bundleHash ? { bundleHash } : {}),
183
+ ...extra,
184
+ };
185
+ if (isDev) {
186
+ delete body.buildTime;
187
+ }
188
+ return body;
189
+ }
190
+
191
+ export interface UpdateIdentity {
192
+ packageVersion: string;
193
+ currentVersion?: string;
194
+ /** Stable client uuid — the gray-release bucketing key. */
195
+ uuid: string;
196
+ }
197
+
198
+ export function resolveCheckResult(
199
+ rootInfo: CheckResult,
200
+ identity: UpdateIdentity,
201
+ log: (...args: any[]) => void = () => {}
202
+ ): CheckResult {
203
+ const { expVersion, ...rootResult } = rootInfo;
204
+ const rollout = expVersion?.config?.rollout?.[identity.packageVersion];
205
+ if (rootResult.update && expVersion && typeof rollout === 'number') {
206
+ if (isInRollout(rollout, identity.uuid)) {
207
+ log(`${expVersion.name} in ${rollout}% rollout, continue`);
208
+ if (
209
+ typeof expVersion.hash === 'string' &&
210
+ expVersion.hash.length > 0 &&
211
+ expVersion.hash === identity.currentVersion
212
+ ) {
213
+ return { upToDate: true };
214
+ }
215
+ const info: CheckResult = {
216
+ update: true,
217
+ ...expVersion,
218
+ };
219
+ if (rootResult.paths) {
220
+ info.paths = rootResult.paths;
221
+ }
222
+ return info;
223
+ }
224
+ log(`${expVersion.name} not in ${rollout}% rollout, ignored`);
225
+ }
226
+ if (
227
+ rootResult.update &&
228
+ typeof rootResult.hash === 'string' &&
229
+ rootResult.hash.length > 0 &&
230
+ rootResult.hash === identity.currentVersion
231
+ ) {
232
+ return { upToDate: true };
233
+ }
234
+ return rootResult;
235
+ }
236
+
237
+ /**
238
+ * Whether the native orchestrator should activate a downloaded version for
239
+ * the next launch: either the client opted in via its silent strategies
240
+ * (afterDownload === 'setNeedUpdate'), or the server marked this version
241
+ * `config.forceBoot` — the per-version remote override that closes the
242
+ * brick-rescue gap for alert-strategy apps (a bricked device never runs JS,
243
+ * so activation cannot wait for it). Native-only: the JS side's interactive
244
+ * strategies are not consulted and not affected. The device-local
245
+ * rolledBackVersion guard in decideDownload still wins over forceBoot, and
246
+ * the activated version keeps the first_time crash-protection rollback.
247
+ */
248
+ export function shouldActivateAfterDownload(
249
+ info: CheckResult,
250
+ afterDownload?: string
251
+ ): boolean {
252
+ return afterDownload === 'setNeedUpdate' || !!info?.config?.forceBoot;
253
+ }
254
+
255
+ export type DownloadStrategyType = 'diff' | 'pdiff' | 'full';
256
+
257
+ export interface DownloadAttempt {
258
+ type: DownloadStrategyType;
259
+ /** Candidate URLs (paths × file name); the executor probes and picks one. */
260
+ urls: string[];
261
+ }
262
+
263
+ export interface DownloadPlan {
264
+ hash: string;
265
+ /** Ordered attempts: incremental first, full as the last resort. */
266
+ attempts: DownloadAttempt[];
267
+ /** Dev-only: nothing to fetch — treat as an immediate no-op success. */
268
+ devNoop: boolean;
269
+ }
270
+
271
+ export type DownloadDecision =
272
+ | {
273
+ action: 'none';
274
+ reason: 'noUpdate' | 'alreadyCurrent' | 'rolledBack' | 'noArtifact';
275
+ }
276
+ | ({ action: 'download' } & DownloadPlan);
277
+
278
+ export function decideDownload(
279
+ info: CheckResult,
280
+ identity: { currentVersion?: string; rolledBackVersion?: string },
281
+ isDev = false
282
+ ): DownloadDecision {
283
+ const { hash, diff, pdiff, full, paths = [] } = info;
284
+ if (!info.update || !hash) {
285
+ return { action: 'none', reason: 'noUpdate' };
286
+ }
287
+ if (hash === identity.currentVersion) {
288
+ return { action: 'none', reason: 'alreadyCurrent' };
289
+ }
290
+ if (identity.rolledBackVersion && hash === identity.rolledBackVersion) {
291
+ return { action: 'none', reason: 'rolledBack' };
292
+ }
293
+ const attempts: DownloadAttempt[] = [];
294
+ // Incremental artifacts only exist against release builds; dev goes
295
+ // straight to full (or the no-op below when there is none).
296
+ if (!isDev) {
297
+ const diffUrls = joinUrls(paths, diff);
298
+ if (diffUrls?.length) {
299
+ attempts.push({ type: 'diff', urls: diffUrls });
300
+ }
301
+ const pdiffUrls = joinUrls(paths, pdiff);
302
+ if (pdiffUrls?.length) {
303
+ attempts.push({ type: 'pdiff', urls: pdiffUrls });
304
+ }
305
+ }
306
+ const fullUrls = joinUrls(paths, full);
307
+ if (fullUrls?.length) {
308
+ attempts.push({ type: 'full', urls: fullUrls });
309
+ }
310
+ const devNoop = !!isDev && !fullUrls?.length;
311
+ if (!attempts.length && !devNoop) {
312
+ return { action: 'none', reason: 'noArtifact' };
313
+ }
314
+ return {
315
+ action: 'download',
316
+ hash,
317
+ attempts,
318
+ devNoop,
319
+ };
320
+ }
package/src/utils.ts CHANGED
@@ -76,18 +76,7 @@ const ping = isWeb
76
76
  }
77
77
  };
78
78
 
79
- export function joinUrls(paths: string[], fileName?: string) {
80
- if (fileName) {
81
- return paths.map((path) => {
82
- const normalizedPath = path.replace(/\/+$/, '');
83
- // Keep explicit http(s) URLs for local/self-hosted update sources.
84
- const baseUrl = /^[a-z][a-z0-9+.-]*:\/\//i.test(normalizedPath)
85
- ? normalizedPath
86
- : `https://${normalizedPath}`;
87
- return `${baseUrl}/${fileName}`;
88
- });
89
- }
90
- }
79
+ export { joinUrls } from './updateFlowCore';
91
80
 
92
81
  export const testUrls = async (urls?: string[]): Promise<string | null> => {
93
82
  if (!urls?.length) {
@@ -1,87 +0,0 @@
1
- /* eslint-disable no-fallthrough */
2
-
3
- import { cInfo } from './core';
4
-
5
- /* eslint-disable no-bitwise */
6
- export function murmurhash3_32_gc(key: string, seed = 0) {
7
- let remainder: number,
8
- bytes: number,
9
- h1: number,
10
- h1b: number,
11
- c1: number,
12
- c2: number,
13
- k1: number,
14
- i: number;
15
-
16
- remainder = key.length & 3; // key.length % 4
17
- bytes = key.length - remainder;
18
- h1 = seed;
19
- c1 = 0xcc9e2d51;
20
- c2 = 0x1b873593;
21
- i = 0;
22
-
23
- while (i < bytes) {
24
- k1 =
25
- (key.charCodeAt(i) & 0xff) |
26
- ((key.charCodeAt(++i) & 0xff) << 8) |
27
- ((key.charCodeAt(++i) & 0xff) << 16) |
28
- ((key.charCodeAt(++i) & 0xff) << 24);
29
- ++i;
30
-
31
- k1 =
32
- ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
33
- k1 = (k1 << 15) | (k1 >>> 17);
34
- k1 =
35
- ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
36
-
37
- h1 ^= k1;
38
- h1 = (h1 << 13) | (h1 >>> 19);
39
- h1b =
40
- ((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) & 0xffffffff;
41
- h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
42
- }
43
-
44
- k1 = 0;
45
-
46
- switch (remainder) {
47
- // biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough
48
- case 3:
49
- k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
50
- // biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough
51
- case 2:
52
- k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
53
- case 1:
54
- k1 ^= key.charCodeAt(i) & 0xff;
55
-
56
- k1 =
57
- ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) &
58
- 0xffffffff;
59
- k1 = (k1 << 15) | (k1 >>> 17);
60
- k1 =
61
- ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) &
62
- 0xffffffff;
63
- h1 ^= k1;
64
- }
65
-
66
- h1 ^= key.length;
67
-
68
- h1 ^= h1 >>> 16;
69
- h1 =
70
- ((h1 & 0xffff) * 0x85ebca6b +
71
- ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) &
72
- 0xffffffff;
73
- h1 ^= h1 >>> 13;
74
- h1 =
75
- ((h1 & 0xffff) * 0xc2b2ae35 +
76
- ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) &
77
- 0xffffffff;
78
- h1 ^= h1 >>> 16;
79
-
80
- return h1 >>> 0;
81
- }
82
-
83
- const intForUUID = murmurhash3_32_gc(cInfo.uuid);
84
-
85
- export function isInRollout(rollout: number) {
86
- return intForUUID % 100 < rollout;
87
- }
@@ -1,30 +0,0 @@
1
- import { currentVersion, packageVersion } from './core';
2
- import { isInRollout } from './isInRollout';
3
- import type { CheckResult } from './type';
4
- import { log } from './utils';
5
-
6
- export function resolveCheckResult(rootInfo: CheckResult): CheckResult {
7
- const { expVersion, ...rootResult } = rootInfo;
8
- const rollout = expVersion?.config?.rollout?.[packageVersion];
9
- if (rootResult.update && expVersion && typeof rollout === 'number') {
10
- if (isInRollout(rollout)) {
11
- log(`${expVersion.name} in ${rollout}% rollout, continue`);
12
- if (expVersion.hash === currentVersion) {
13
- return { upToDate: true };
14
- }
15
- const info: CheckResult = {
16
- update: true,
17
- ...expVersion,
18
- };
19
- if (rootResult.paths) {
20
- info.paths = rootResult.paths;
21
- }
22
- return info;
23
- }
24
- log(`${expVersion.name} not in ${rollout}% rollout, ignored`);
25
- }
26
- if (rootResult.update && rootResult.hash === currentVersion) {
27
- return { upToDate: true };
28
- }
29
- return rootResult;
30
- }