react-native-update 10.49.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.
- package/BUNDLEHASH_MIGRATION.md +22 -2
- package/NATIVE_CHECKUPDATE_DESIGN.md +251 -21
- package/NATIVE_CHECK_FOLLOWUPS.md +365 -0
- package/README-CN.md +14 -1
- package/README.md +14 -1
- package/android/jni/Android.mk +5 -1
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +42 -9
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +98 -22
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +455 -0
- package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateFlow.java +23 -0
- package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +16 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +196 -42
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +56 -3
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +10 -0
- package/cpp/patch_core/digest.cpp +53 -0
- package/cpp/patch_core/digest.h +18 -0
- package/cpp/patch_core/patch_core.cpp +17 -1
- package/cpp/patch_core/patch_core.h +8 -0
- package/cpp/patch_core/tests/patch_core_test.cpp +99 -0
- package/cpp/update_flow_core/flow_json.cpp +522 -0
- package/cpp/update_flow_core/flow_json.h +103 -0
- package/cpp/update_flow_core/tests/flow_vectors.json +1401 -0
- package/cpp/update_flow_core/tests/update_flow_core_test.cpp +317 -0
- package/cpp/update_flow_core/update_flow_core.cpp +301 -0
- package/cpp/update_flow_core/update_flow_core.h +77 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +121 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +7 -0
- package/harmony/pushy/src/main/cpp/pushy.cpp +150 -1
- package/harmony/pushy/src/main/ets/DownloadTask.ts +133 -9
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +3 -0
- package/harmony/pushy/src/main/ets/MonotonicClock.ts +13 -0
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +500 -0
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +16 -0
- package/harmony/pushy/src/main/ets/PathUtils.ts +21 -0
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +22 -0
- package/harmony/pushy/src/main/ets/UpdateContext.ts +152 -52
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +840 -68
- package/ios/RCTPushy/RCTPushyDownloader.h +1 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +4 -3
- package/package.json +3 -1
- package/react-native-update.podspec +2 -0
- package/src/NativePushy.ts +14 -0
- package/src/client.ts +439 -122
- package/src/endpoint.ts +105 -80
- package/src/error.ts +4 -0
- package/src/locales/en.ts +4 -0
- package/src/locales/zh.ts +4 -0
- package/src/provider.tsx +74 -9
- package/src/telemetry.ts +8 -0
- package/src/type.ts +31 -0
- package/src/updateFlowCore.ts +320 -0
- package/src/utils.ts +14 -12
- package/src/isInRollout.ts +0 -86
- package/src/resolveCheckResult.ts +0 -30
|
@@ -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
|
|
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) {
|
|
@@ -147,6 +136,15 @@ export const fetchWithTimeout = (
|
|
|
147
136
|
// Promise.race the losing fetch kept running (and kept the connection busy)
|
|
148
137
|
// long after the caller had already moved on.
|
|
149
138
|
const controller = new AbortController();
|
|
139
|
+
// The timeout controller replaces params.signal on the fetch call, so a
|
|
140
|
+
// caller-provided signal (e.g. the hedged endpoint race cancelling losers)
|
|
141
|
+
// must be chained onto it manually.
|
|
142
|
+
const externalSignal = (params as any)?.signal as AbortSignal | undefined;
|
|
143
|
+
if (externalSignal?.aborted) {
|
|
144
|
+
controller.abort();
|
|
145
|
+
} else if (externalSignal) {
|
|
146
|
+
externalSignal.addEventListener('abort', () => controller.abort());
|
|
147
|
+
}
|
|
150
148
|
const timeoutId = setTimeout(() => {
|
|
151
149
|
log('fetch timeout', url);
|
|
152
150
|
controller.abort();
|
|
@@ -154,6 +152,10 @@ export const fetchWithTimeout = (
|
|
|
154
152
|
|
|
155
153
|
return enhancedFetch(url, { ...params, signal: controller.signal as any })
|
|
156
154
|
.catch((e: any) => {
|
|
155
|
+
if (externalSignal?.aborted) {
|
|
156
|
+
// Cancelled by the caller, not timed out; keep the abort semantics.
|
|
157
|
+
throw e;
|
|
158
|
+
}
|
|
157
159
|
if (controller.signal.aborted) {
|
|
158
160
|
throw Error(i18n.t('error_ping_timeout'));
|
|
159
161
|
}
|
package/src/isInRollout.ts
DELETED
|
@@ -1,86 +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 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
|
|
32
|
-
k1 = (k1 << 15) | (k1 >>> 17);
|
|
33
|
-
k1 =
|
|
34
|
-
((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
|
|
35
|
-
|
|
36
|
-
h1 ^= k1;
|
|
37
|
-
h1 = (h1 << 13) | (h1 >>> 19);
|
|
38
|
-
h1b =
|
|
39
|
-
((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) & 0xffffffff;
|
|
40
|
-
h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
k1 = 0;
|
|
44
|
-
|
|
45
|
-
switch (remainder) {
|
|
46
|
-
// biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough
|
|
47
|
-
case 3:
|
|
48
|
-
k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
|
49
|
-
// biome-ignore lint/suspicious/noFallthroughSwitchClause: MurmurHash fallthrough
|
|
50
|
-
case 2:
|
|
51
|
-
k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
|
52
|
-
case 1:
|
|
53
|
-
k1 ^= key.charCodeAt(i) & 0xff;
|
|
54
|
-
|
|
55
|
-
k1 =
|
|
56
|
-
((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) &
|
|
57
|
-
0xffffffff;
|
|
58
|
-
k1 = (k1 << 15) | (k1 >>> 17);
|
|
59
|
-
k1 =
|
|
60
|
-
((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) &
|
|
61
|
-
0xffffffff;
|
|
62
|
-
h1 ^= k1;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
h1 ^= key.length;
|
|
66
|
-
|
|
67
|
-
h1 ^= h1 >>> 16;
|
|
68
|
-
h1 =
|
|
69
|
-
((h1 & 0xffff) * 0x85ebca6b +
|
|
70
|
-
((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) &
|
|
71
|
-
0xffffffff;
|
|
72
|
-
h1 ^= h1 >>> 13;
|
|
73
|
-
h1 =
|
|
74
|
-
((h1 & 0xffff) * 0xc2b2ae35 +
|
|
75
|
-
((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) &
|
|
76
|
-
0xffffffff;
|
|
77
|
-
h1 ^= h1 >>> 16;
|
|
78
|
-
|
|
79
|
-
return h1 >>> 0;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const intForUUID = murmurhash3_32_gc(cInfo.uuid);
|
|
83
|
-
|
|
84
|
-
export function isInRollout(rollout: number) {
|
|
85
|
-
return intForUUID % 100 < rollout;
|
|
86
|
-
}
|
|
@@ -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
|
-
}
|