@hot-updater/react-native 1.0.0-rc.17 → 1.0.0-rc.21

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 (240) hide show
  1. package/lib/commonjs/checkForUpdate.js +7 -9
  2. package/lib/commonjs/checkForUpdate.js.map +1 -1
  3. package/lib/commonjs/clientPlugin.js +7 -43
  4. package/lib/commonjs/clientPlugin.js.map +1 -1
  5. package/lib/commonjs/httpClient.js +7 -9
  6. package/lib/commonjs/httpClient.js.map +1 -1
  7. package/lib/commonjs/index.js +10 -0
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/native.js +4 -4
  10. package/lib/commonjs/native.js.map +1 -1
  11. package/lib/commonjs/pluginHost.js +26 -190
  12. package/lib/commonjs/pluginHost.js.map +1 -1
  13. package/lib/commonjs/releaseCatalogCache.js +7 -59
  14. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  15. package/lib/commonjs/sdkVersion.js +1 -1
  16. package/lib/module/checkForUpdate.js +1 -3
  17. package/lib/module/checkForUpdate.js.map +1 -1
  18. package/lib/module/clientPlugin.js +1 -41
  19. package/lib/module/clientPlugin.js.map +1 -1
  20. package/lib/module/httpClient.js +1 -3
  21. package/lib/module/httpClient.js.map +1 -1
  22. package/lib/module/index.js +2 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/native.js +1 -1
  25. package/lib/module/native.js.map +1 -1
  26. package/lib/module/pluginHost.js +22 -187
  27. package/lib/module/pluginHost.js.map +1 -1
  28. package/lib/module/releaseCatalogCache.js +3 -54
  29. package/lib/module/releaseCatalogCache.js.map +1 -1
  30. package/lib/module/sdkVersion.js +1 -1
  31. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/clientPlugin.d.ts +1 -184
  33. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/httpClient.d.ts +1 -2
  35. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/index.d.ts +1 -0
  37. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/native.d.ts +1 -1
  39. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/pluginHost.d.ts +3 -28
  41. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +1 -13
  43. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  45. package/lib/typescript/commonjs/types.d.ts +1 -1
  46. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/wrap.types.d.ts +2 -2
  48. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  49. package/lib/typescript/module/clientPlugin.d.ts +1 -184
  50. package/lib/typescript/module/clientPlugin.d.ts.map +1 -1
  51. package/lib/typescript/module/httpClient.d.ts +1 -2
  52. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  53. package/lib/typescript/module/index.d.ts +1 -0
  54. package/lib/typescript/module/index.d.ts.map +1 -1
  55. package/lib/typescript/module/native.d.ts +1 -1
  56. package/lib/typescript/module/native.d.ts.map +1 -1
  57. package/lib/typescript/module/pluginHost.d.ts +3 -28
  58. package/lib/typescript/module/pluginHost.d.ts.map +1 -1
  59. package/lib/typescript/module/releaseCatalogCache.d.ts +1 -13
  60. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  61. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  62. package/lib/typescript/module/types.d.ts +1 -1
  63. package/lib/typescript/module/types.d.ts.map +1 -1
  64. package/lib/typescript/module/wrap.types.d.ts +2 -2
  65. package/package.json +7 -16
  66. package/src/checkForUpdate.ts +4 -6
  67. package/src/clientPlugin.ts +17 -231
  68. package/src/httpClient.ts +4 -4
  69. package/src/index.ts +7 -0
  70. package/src/native.ts +1 -1
  71. package/src/pluginHost.ts +24 -241
  72. package/src/releaseCatalogCache.ts +7 -136
  73. package/src/sdkVersion.ts +1 -1
  74. package/src/types.ts +1 -1
  75. package/src/wrap.types.ts +2 -2
  76. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  77. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -2236
  78. package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +0 -220
  79. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -264
  80. package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +0 -97
  81. package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +0 -71
  82. package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +0 -106
  83. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  84. package/android/src/test/java/com/hotupdater/NoBackupContext.kt +0 -11
  85. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -397
  86. package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +0 -44
  87. package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +0 -57
  88. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  89. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  90. package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +0 -206
  91. package/lib/commonjs/appReady.spec.js +0 -360
  92. package/lib/commonjs/appReady.spec.js.map +0 -1
  93. package/lib/commonjs/appReady.test-utils.js +0 -25
  94. package/lib/commonjs/appReady.test-utils.js.map +0 -1
  95. package/lib/commonjs/baseURL.js +0 -16
  96. package/lib/commonjs/baseURL.js.map +0 -1
  97. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  98. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  99. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -936
  100. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  101. package/lib/commonjs/entryImportGraph.spec.js +0 -50
  102. package/lib/commonjs/entryImportGraph.spec.js.map +0 -1
  103. package/lib/commonjs/httpClient.spec.js +0 -171
  104. package/lib/commonjs/httpClient.spec.js.map +0 -1
  105. package/lib/commonjs/index.spec.js +0 -395
  106. package/lib/commonjs/index.spec.js.map +0 -1
  107. package/lib/commonjs/iosInterop.spec.js +0 -18
  108. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  109. package/lib/commonjs/native.spec.js +0 -910
  110. package/lib/commonjs/native.spec.js.map +0 -1
  111. package/lib/commonjs/pluginHost.spec.js +0 -292
  112. package/lib/commonjs/pluginHost.spec.js.map +0 -1
  113. package/lib/commonjs/plugins/insights/eventId.js +0 -129
  114. package/lib/commonjs/plugins/insights/eventId.js.map +0 -1
  115. package/lib/commonjs/plugins/insights/eventId.spec.js +0 -47
  116. package/lib/commonjs/plugins/insights/eventId.spec.js.map +0 -1
  117. package/lib/commonjs/plugins/insights/index.js +0 -272
  118. package/lib/commonjs/plugins/insights/index.js.map +0 -1
  119. package/lib/commonjs/plugins/insights/insights.spec.js +0 -640
  120. package/lib/commonjs/plugins/insights/insights.spec.js.map +0 -1
  121. package/lib/commonjs/plugins/insights/sender.js +0 -128
  122. package/lib/commonjs/plugins/insights/sender.js.map +0 -1
  123. package/lib/commonjs/plugins/insights/sender.spec.js +0 -265
  124. package/lib/commonjs/plugins/insights/sender.spec.js.map +0 -1
  125. package/lib/commonjs/plugins/insights/state.js +0 -145
  126. package/lib/commonjs/plugins/insights/state.js.map +0 -1
  127. package/lib/commonjs/releaseCatalogCache.spec.js +0 -213
  128. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  129. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  130. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  131. package/lib/commonjs/store.notification.spec.js +0 -84
  132. package/lib/commonjs/store.notification.spec.js.map +0 -1
  133. package/lib/commonjs/store.spec.js +0 -182
  134. package/lib/commonjs/store.spec.js.map +0 -1
  135. package/lib/commonjs/updateError.spec.js +0 -149
  136. package/lib/commonjs/updateError.spec.js.map +0 -1
  137. package/lib/commonjs/wrap.progress.spec.js +0 -195
  138. package/lib/commonjs/wrap.progress.spec.js.map +0 -1
  139. package/lib/commonjs/wrap.spec.js +0 -232
  140. package/lib/commonjs/wrap.spec.js.map +0 -1
  141. package/lib/module/appReady.spec.js +0 -359
  142. package/lib/module/appReady.spec.js.map +0 -1
  143. package/lib/module/appReady.test-utils.js +0 -19
  144. package/lib/module/appReady.test-utils.js.map +0 -1
  145. package/lib/module/baseURL.js +0 -11
  146. package/lib/module/baseURL.js.map +0 -1
  147. package/lib/module/catalogCacheNative.spec.js +0 -31
  148. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  149. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -935
  150. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  151. package/lib/module/entryImportGraph.spec.js +0 -50
  152. package/lib/module/entryImportGraph.spec.js.map +0 -1
  153. package/lib/module/httpClient.spec.js +0 -171
  154. package/lib/module/httpClient.spec.js.map +0 -1
  155. package/lib/module/index.spec.js +0 -394
  156. package/lib/module/index.spec.js.map +0 -1
  157. package/lib/module/iosInterop.spec.js +0 -18
  158. package/lib/module/iosInterop.spec.js.map +0 -1
  159. package/lib/module/native.spec.js +0 -909
  160. package/lib/module/native.spec.js.map +0 -1
  161. package/lib/module/pluginHost.spec.js +0 -291
  162. package/lib/module/pluginHost.spec.js.map +0 -1
  163. package/lib/module/plugins/insights/eventId.js +0 -122
  164. package/lib/module/plugins/insights/eventId.js.map +0 -1
  165. package/lib/module/plugins/insights/eventId.spec.js +0 -47
  166. package/lib/module/plugins/insights/eventId.spec.js.map +0 -1
  167. package/lib/module/plugins/insights/index.js +0 -268
  168. package/lib/module/plugins/insights/index.js.map +0 -1
  169. package/lib/module/plugins/insights/insights.spec.js +0 -639
  170. package/lib/module/plugins/insights/insights.spec.js.map +0 -1
  171. package/lib/module/plugins/insights/sender.js +0 -123
  172. package/lib/module/plugins/insights/sender.js.map +0 -1
  173. package/lib/module/plugins/insights/sender.spec.js +0 -265
  174. package/lib/module/plugins/insights/sender.spec.js.map +0 -1
  175. package/lib/module/plugins/insights/state.js +0 -139
  176. package/lib/module/plugins/insights/state.js.map +0 -1
  177. package/lib/module/releaseCatalogCache.spec.js +0 -213
  178. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  179. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  180. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  181. package/lib/module/store.notification.spec.js +0 -85
  182. package/lib/module/store.notification.spec.js.map +0 -1
  183. package/lib/module/store.spec.js +0 -181
  184. package/lib/module/store.spec.js.map +0 -1
  185. package/lib/module/updateError.spec.js +0 -149
  186. package/lib/module/updateError.spec.js.map +0 -1
  187. package/lib/module/wrap.progress.spec.js +0 -196
  188. package/lib/module/wrap.progress.spec.js.map +0 -1
  189. package/lib/module/wrap.spec.js +0 -233
  190. package/lib/module/wrap.spec.js.map +0 -1
  191. package/lib/typescript/commonjs/appReady.test-utils.d.ts +0 -9
  192. package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +0 -1
  193. package/lib/typescript/commonjs/baseURL.d.ts +0 -4
  194. package/lib/typescript/commonjs/baseURL.d.ts.map +0 -1
  195. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +0 -18
  196. package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +0 -1
  197. package/lib/typescript/commonjs/plugins/insights/index.d.ts +0 -40
  198. package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +0 -1
  199. package/lib/typescript/commonjs/plugins/insights/sender.d.ts +0 -74
  200. package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +0 -1
  201. package/lib/typescript/commonjs/plugins/insights/state.d.ts +0 -46
  202. package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +0 -1
  203. package/lib/typescript/module/appReady.test-utils.d.ts +0 -9
  204. package/lib/typescript/module/appReady.test-utils.d.ts.map +0 -1
  205. package/lib/typescript/module/baseURL.d.ts +0 -4
  206. package/lib/typescript/module/baseURL.d.ts.map +0 -1
  207. package/lib/typescript/module/plugins/insights/eventId.d.ts +0 -18
  208. package/lib/typescript/module/plugins/insights/eventId.d.ts.map +0 -1
  209. package/lib/typescript/module/plugins/insights/index.d.ts +0 -40
  210. package/lib/typescript/module/plugins/insights/index.d.ts.map +0 -1
  211. package/lib/typescript/module/plugins/insights/sender.d.ts +0 -74
  212. package/lib/typescript/module/plugins/insights/sender.d.ts.map +0 -1
  213. package/lib/typescript/module/plugins/insights/state.d.ts +0 -46
  214. package/lib/typescript/module/plugins/insights/state.d.ts.map +0 -1
  215. package/plugins/insights/package.json +0 -8
  216. package/src/appReady.spec.ts +0 -393
  217. package/src/appReady.test-utils.ts +0 -31
  218. package/src/baseURL.ts +0 -15
  219. package/src/catalogCacheNative.spec.ts +0 -63
  220. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -966
  221. package/src/entryImportGraph.spec.ts +0 -65
  222. package/src/httpClient.spec.ts +0 -216
  223. package/src/index.spec.ts +0 -434
  224. package/src/iosInterop.spec.ts +0 -30
  225. package/src/native.spec.ts +0 -978
  226. package/src/pluginHost.spec.ts +0 -297
  227. package/src/plugins/insights/eventId.spec.ts +0 -60
  228. package/src/plugins/insights/eventId.ts +0 -171
  229. package/src/plugins/insights/index.ts +0 -367
  230. package/src/plugins/insights/insights.spec.ts +0 -732
  231. package/src/plugins/insights/sender.spec.ts +0 -287
  232. package/src/plugins/insights/sender.ts +0 -221
  233. package/src/plugins/insights/state.ts +0 -184
  234. package/src/releaseCatalogCache.spec.ts +0 -304
  235. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  236. package/src/store.notification.spec.ts +0 -102
  237. package/src/store.spec.ts +0 -215
  238. package/src/updateError.spec.ts +0 -144
  239. package/src/wrap.progress.spec.ts +0 -232
  240. package/src/wrap.spec.ts +0 -262
package/src/pluginHost.ts CHANGED
@@ -1,12 +1,6 @@
1
+ import { createPluginHost } from "@hot-updater/protocol";
1
2
  import { Platform } from "react-native";
2
3
 
3
- import { resolveBaseURL } from "./baseURL";
4
- import type {
5
- HotUpdaterClientContext,
6
- HotUpdaterClientHooks,
7
- HotUpdaterClientPlugin,
8
- HotUpdaterClientStorage,
9
- } from "./clientPlugin";
10
4
  import {
11
5
  getAppVersion,
12
6
  getBundleId,
@@ -18,245 +12,34 @@ import {
18
12
  setStorageItem,
19
13
  } from "./native";
20
14
  import { HOT_UPDATER_SDK_VERSION } from "./sdkVersion";
21
- import type { HotUpdaterBaseURL } from "./types";
22
15
 
23
- type HookName = keyof HotUpdaterClientHooks;
24
- type HookPayload<K extends HookName> = Parameters<
25
- NonNullable<HotUpdaterClientHooks[K]>
26
- >[0];
27
-
28
- /** The init or wrap settings plugins see through their context. */
29
- export interface PluginHostConfig {
30
- readonly baseURL: HotUpdaterBaseURL;
31
- readonly requestHeaders?: Record<string, string>;
32
- readonly requestTimeout?: number;
33
- readonly onError?: (error: unknown) => void;
34
- }
35
-
36
- /** Storage values stay small, since native reads them on the JS thread. */
37
- const MAX_STORAGE_VALUE_LENGTH = 64 * 1024;
38
-
39
- let config: PluginHostConfig | null = null;
40
- /** Hooks by plugin, so a plugin configured again is not set up twice. */
41
- const setUpPlugins = new WeakMap<
42
- HotUpdaterClientPlugin,
43
- HotUpdaterClientHooks
44
- >();
45
- let activePlugins: readonly {
46
- readonly plugin: HotUpdaterClientPlugin;
47
- readonly hooks: HotUpdaterClientHooks;
48
- }[] = [];
49
- /** What plugins read and wrote, so a read follows a write before native does. */
50
- const storageValues = new Map<string, string | null>();
51
-
52
- const reportHookError = (message: string, error: unknown) => {
53
- const report = new Error(`[HotUpdater] ${message}`, { cause: error });
54
- try {
55
- if (config?.onError) {
56
- config.onError(report);
57
- return;
58
- }
59
- } catch {
60
- // A throwing onError must not break the update path either.
61
- }
62
- console.warn(report.message, error);
63
- };
64
-
65
- const fetchFromServer = async (
66
- path: string,
67
- init: RequestInit = {},
68
- ): Promise<Response> => {
69
- if (config === null) {
70
- throw new Error("[HotUpdater] Plugins can fetch only after init or wrap.");
71
- }
72
- if (/^[a-z][a-z\d+.-]*:/i.test(path) || path.startsWith("//")) {
73
- throw new TypeError(
74
- "[HotUpdater] Plugin fetch takes a path relative to baseURL.",
75
- );
76
- }
77
- const { requestHeaders, requestTimeout } = config;
78
- const url = `${await resolveBaseURL(config.baseURL)}/${path.replace(/^\/+/, "")}`;
79
- const headers = new Headers(requestHeaders);
80
- new Headers(init.headers).forEach((value, name) => {
81
- headers.set(name, value);
82
- });
83
-
84
- const controller = new AbortController();
85
- const callerSignal = init.signal;
86
- const abortFromCaller = () => controller.abort();
87
- if (callerSignal?.aborted) controller.abort();
88
- callerSignal?.addEventListener("abort", abortFromCaller);
89
- const timeoutId = setTimeout(
90
- () => controller.abort(),
91
- requestTimeout ?? 5000,
92
- );
93
-
94
- try {
95
- return await fetch(url, { ...init, headers, signal: controller.signal });
96
- } catch (error: unknown) {
97
- if (
98
- error instanceof Error &&
99
- error.name === "AbortError" &&
100
- !callerSignal?.aborted
101
- ) {
102
- throw new Error("Request timed out");
103
- }
104
- throw error;
105
- } finally {
106
- clearTimeout(timeoutId);
107
- callerSignal?.removeEventListener("abort", abortFromCaller);
108
- }
109
- };
110
-
111
- const createStorage = (pluginId: string): HotUpdaterClientStorage => {
112
- const scopedKey = (key: string) => {
113
- if (typeof key !== "string" || key.length === 0) {
114
- throw new TypeError(
115
- "[HotUpdater] Plugin storage keys are non-empty strings.",
116
- );
117
- }
118
- // Encoded, so no id and key pair can name another plugin's key.
119
- return `plugins/${encodeURIComponent(pluginId)}/${key}`;
120
- };
121
-
122
- return {
123
- get: (key) => {
124
- const storageKey = scopedKey(key);
125
- const cached = storageValues.get(storageKey);
126
- if (cached !== undefined) return cached;
127
- const value = getStorageItem(storageKey);
128
- storageValues.set(storageKey, value);
129
- return value;
130
- },
131
- set: (key, value) => {
132
- const storageKey = scopedKey(key);
133
- if (value !== null && typeof value !== "string") {
134
- throw new TypeError(
135
- "[HotUpdater] Plugin storage values are strings or null.",
136
- );
137
- }
138
- if (value !== null && value.length > MAX_STORAGE_VALUE_LENGTH) {
139
- throw new RangeError(
140
- "[HotUpdater] Plugin storage values are at most 64 KB.",
141
- );
142
- }
143
- setStorageItem(storageKey, value);
144
- storageValues.set(storageKey, value);
145
- },
146
- };
147
- };
148
-
149
- const createContext = (pluginId: string): HotUpdaterClientContext => ({
150
- fetch: fetchFromServer,
151
- get installId() {
152
- return getInstallId();
16
+ export type { PluginHostConfig } from "@hot-updater/protocol";
17
+
18
+ /** The app's plugin host: plugins read the native module and fetch the configured server. */
19
+ export const {
20
+ configurePlugins,
21
+ hasPluginHook,
22
+ buildPluginEvent,
23
+ dispatchPluginHook,
24
+ emitPluginHook,
25
+ } = createPluginHost({
26
+ fetch: (url, init) => fetch(url, init),
27
+ get platform() {
28
+ return Platform.OS === "android" ? "android" : "ios";
153
29
  },
154
- platform: Platform.OS === "android" ? "android" : "ios",
155
- get appVersion() {
156
- return getAppVersion();
30
+ get isDebugBuild() {
31
+ return typeof __DEV__ !== "undefined" && __DEV__;
157
32
  },
158
33
  get sdkVersion() {
159
34
  return HOT_UPDATER_SDK_VERSION;
160
35
  },
161
- isDebugBuild: typeof __DEV__ !== "undefined" && __DEV__,
162
- getBundleId,
163
- getChannel,
164
- getCohort,
165
- getFingerprintHash,
166
- storage: createStorage(pluginId),
36
+ getInstallId: () => getInstallId(),
37
+ getAppVersion: () => getAppVersion(),
38
+ getBundleId: () => getBundleId(),
39
+ getChannel: () => getChannel(),
40
+ getCohort: () => getCohort(),
41
+ getFingerprintHash: () => getFingerprintHash(),
42
+ getStorageItem: (key) => getStorageItem(key),
43
+ setStorageItem: (key, value) => setStorageItem(key, value),
167
44
  now: () => Date.now(),
168
45
  });
169
-
170
- /**
171
- * Sets up the plugins of an init or wrap call. A plugin passed to an earlier
172
- * call keeps its hooks, and a plugin left out stops receiving events.
173
- */
174
- export const configurePlugins = (
175
- plugins: readonly HotUpdaterClientPlugin[] | undefined,
176
- nextConfig: PluginHostConfig,
177
- ): void => {
178
- const ids = new Set<string>();
179
- for (const plugin of plugins ?? []) {
180
- if (typeof plugin.id !== "string" || plugin.id.length === 0) {
181
- throw new Error("[HotUpdater] A plugin id must be a non-empty string.");
182
- }
183
- if (ids.has(plugin.id)) {
184
- throw new Error(
185
- `[HotUpdater] Two plugins use the id "${plugin.id}". Plugin ids must be unique.`,
186
- );
187
- }
188
- ids.add(plugin.id);
189
- }
190
-
191
- config = nextConfig;
192
- activePlugins = (plugins ?? []).map((plugin) => {
193
- let hooks = setUpPlugins.get(plugin);
194
- if (hooks === undefined) {
195
- try {
196
- hooks = plugin.setup(createContext(plugin.id)) ?? {};
197
- } catch (error) {
198
- reportHookError(`Plugin "${plugin.id}" failed in setup`, error);
199
- hooks = {};
200
- }
201
- setUpPlugins.set(plugin, hooks);
202
- }
203
- return { plugin, hooks };
204
- });
205
- };
206
-
207
- /** Whether any plugin listens to this hook, so callers build events only when one does. */
208
- export const hasPluginHook = (name: HookName): boolean =>
209
- activePlugins.some(({ hooks }) => typeof hooks[name] === "function");
210
-
211
- /**
212
- * Builds a hook's event when a plugin listens. A failure to build it is
213
- * reported, not thrown, since reporting never changes an update.
214
- */
215
- export const buildPluginEvent = <K extends HookName>(
216
- name: K,
217
- createPayload: () => HookPayload<K> | null,
218
- ): HookPayload<K> | null => {
219
- if (!hasPluginHook(name)) return null;
220
- try {
221
- return createPayload();
222
- } catch (error) {
223
- reportHookError(`Failed to build the ${name} event for plugins`, error);
224
- return null;
225
- }
226
- };
227
-
228
- /** Calls a hook on every plugin that listens, without waiting for any. */
229
- export const dispatchPluginHook = <K extends HookName>(
230
- name: K,
231
- payload: HookPayload<K>,
232
- ): void => {
233
- for (const { plugin, hooks } of activePlugins) {
234
- const hook = hooks[name] as
235
- | ((payload: HookPayload<K>) => unknown)
236
- | undefined;
237
- if (typeof hook !== "function") continue;
238
- try {
239
- const result = hook.call(hooks, payload);
240
- if (
241
- result !== null &&
242
- typeof result === "object" &&
243
- typeof (result as PromiseLike<unknown>).then === "function"
244
- ) {
245
- void Promise.resolve(result).catch((error: unknown) => {
246
- reportHookError(`Plugin "${plugin.id}" failed in ${name}`, error);
247
- });
248
- }
249
- } catch (error) {
250
- reportHookError(`Plugin "${plugin.id}" failed in ${name}`, error);
251
- }
252
- }
253
- };
254
-
255
- /** Builds a hook's event when a plugin listens and calls the hook right away. */
256
- export const emitPluginHook = <K extends HookName>(
257
- name: K,
258
- createPayload: () => HookPayload<K> | null,
259
- ): void => {
260
- const payload = buildPluginEvent(name, createPayload);
261
- if (payload !== null) dispatchPluginHook(name, payload);
262
- };
@@ -1,19 +1,15 @@
1
1
  import {
2
- MAX_TARGET_COHORTS_PER_RELEASE,
3
- MAX_COMPILED_CATALOG_BYTES,
4
- MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE,
5
- NUMERIC_COHORT_SIZE,
6
- isUUIDv7,
7
- parseReleaseCatalogScopeKey,
2
+ type ExpectedReleaseCatalogScope,
3
+ getUtf8ByteLength,
4
+ MAX_RELEASE_CATALOG_WIRE_BYTES,
5
+ parseReleaseCatalog,
8
6
  type ReleaseCatalog,
9
- } from "@hot-updater/core";
7
+ } from "@hot-updater/protocol";
10
8
 
11
9
  import { InvalidUpdateResponseError, UpdateHttpError } from "./updateError";
12
10
 
13
11
  const CACHE_FORMAT_VERSION = "1";
14
12
  const MAX_ETAG_BYTES = 1024;
15
- export const MAX_RELEASE_CATALOG_WIRE_BYTES =
16
- MAX_COMPILED_CATALOG_BYTES * 2 + 4 * 1024;
17
13
  export const MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES =
18
14
  MAX_RELEASE_CATALOG_WIRE_BYTES + MAX_ETAG_BYTES + 3;
19
15
 
@@ -48,20 +44,6 @@ let lastParsedCache:
48
44
  })
49
45
  | null = null;
50
46
 
51
- export type ExpectedReleaseCatalogScope = {
52
- readonly channelKey: string;
53
- readonly platform: "ios" | "android";
54
- } & (
55
- | {
56
- readonly strategy: "APP_VERSION";
57
- readonly fingerprintHash?: never;
58
- }
59
- | {
60
- readonly strategy: "FINGERPRINT";
61
- readonly fingerprintHash: string;
62
- }
63
- );
64
-
65
47
  type FetchReleaseCatalogInput = {
66
48
  readonly baseURL: string;
67
49
  readonly expectedScope: ExpectedReleaseCatalogScope;
@@ -70,117 +52,6 @@ type FetchReleaseCatalogInput = {
70
52
  readonly url: string;
71
53
  };
72
54
 
73
- const getUtf8ByteLength = (value: string): number => {
74
- let bytes = 0;
75
- for (let index = 0; index < value.length; index += 1) {
76
- const code = value.charCodeAt(index);
77
- if (code <= 0x7f) {
78
- bytes += 1;
79
- } else if (code <= 0x7ff) {
80
- bytes += 2;
81
- } else if (
82
- code >= 0xd800 &&
83
- code <= 0xdbff &&
84
- index + 1 < value.length &&
85
- value.charCodeAt(index + 1) >= 0xdc00 &&
86
- value.charCodeAt(index + 1) <= 0xdfff
87
- ) {
88
- bytes += 4;
89
- index += 1;
90
- } else {
91
- bytes += 3;
92
- }
93
- }
94
- return bytes;
95
- };
96
-
97
- const isStringArray = (value: unknown): value is readonly string[] =>
98
- Array.isArray(value) && value.every((entry) => typeof entry === "string");
99
-
100
- const isValidReleaseDescriptor = (value: unknown): boolean => {
101
- if (value === null || typeof value !== "object") return false;
102
- const descriptor = value as Record<string, unknown>;
103
-
104
- return (
105
- isUUIDv7(descriptor.releaseId) &&
106
- (descriptor.kind === "BUNDLE" || descriptor.kind === "EMBEDDED") &&
107
- ((descriptor.kind === "BUNDLE" &&
108
- typeof descriptor.bundleId === "string") ||
109
- (descriptor.kind === "EMBEDDED" && descriptor.bundleId === null)) &&
110
- Number.isSafeInteger(descriptor.rolloutCohortCount) &&
111
- (descriptor.rolloutCohortCount as number) >= 0 &&
112
- (descriptor.rolloutCohortCount as number) <= NUMERIC_COHORT_SIZE &&
113
- isStringArray(descriptor.targetCohorts) &&
114
- descriptor.targetCohorts.length <= MAX_TARGET_COHORTS_PER_RELEASE &&
115
- typeof descriptor.shouldForceUpdate === "boolean" &&
116
- (descriptor.message === null || typeof descriptor.message === "string")
117
- );
118
- };
119
-
120
- export const hasExpectedReleaseCatalogScope = (
121
- catalog: ReleaseCatalog,
122
- expected: ExpectedReleaseCatalogScope,
123
- ): boolean => {
124
- try {
125
- const parsed = parseReleaseCatalogScopeKey(catalog.scopeKey);
126
- return (
127
- parsed.channelKey === expected.channelKey &&
128
- parsed.platform === expected.platform &&
129
- parsed.strategy === expected.strategy &&
130
- (parsed.strategy === "APP_VERSION" ||
131
- (expected.strategy === "FINGERPRINT" &&
132
- parsed.fingerprintHash === expected.fingerprintHash))
133
- );
134
- } catch {
135
- return false;
136
- }
137
- };
138
-
139
- const parseValidatedCatalog = (
140
- value: string,
141
- expectedScope: ExpectedReleaseCatalogScope,
142
- ): ReleaseCatalog | null => {
143
- if (getUtf8ByteLength(value) > MAX_RELEASE_CATALOG_WIRE_BYTES) return null;
144
-
145
- try {
146
- const catalog = JSON.parse(value) as Partial<ReleaseCatalog>;
147
- if (
148
- catalog.schemaVersion !== 1 ||
149
- typeof catalog.catalogId !== "string" ||
150
- catalog.catalogId.length === 0 ||
151
- typeof catalog.scopeKey !== "string" ||
152
- !Number.isSafeInteger(catalog.generation) ||
153
- (catalog.generation ?? 0) < 1 ||
154
- typeof catalog.catalogHash !== "string" ||
155
- !/^sha256:[0-9a-f]{64}$/.test(catalog.catalogHash) ||
156
- catalog.fallbackPolicy !== "BUILTIN_IF_ACTIVE_INELIGIBLE" ||
157
- !Array.isArray(catalog.releases) ||
158
- !catalog.releases.every(isValidReleaseDescriptor) ||
159
- (catalog.rollbackReleases !== undefined &&
160
- (!Array.isArray(catalog.rollbackReleases) ||
161
- !catalog.rollbackReleases.every(isValidReleaseDescriptor)))
162
- ) {
163
- return null;
164
- }
165
- if (
166
- !hasExpectedReleaseCatalogScope(catalog as ReleaseCatalog, expectedScope)
167
- ) {
168
- return null;
169
- }
170
- const distinctTargetCohorts = new Set(
171
- [...catalog.releases, ...(catalog.rollbackReleases ?? [])].flatMap(
172
- (release) => release.targetCohorts,
173
- ),
174
- );
175
- if (distinctTargetCohorts.size > MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE) {
176
- return null;
177
- }
178
- return catalog as ReleaseCatalog;
179
- } catch {
180
- return null;
181
- }
182
- };
183
-
184
55
  const isValidETag = (value: string | null): value is string =>
185
56
  value !== null &&
186
57
  value.length > 0 &&
@@ -216,7 +87,7 @@ const parseCache = (
216
87
  const etag = value.slice(versionEnd + 1, etagEnd);
217
88
  const body = value.slice(etagEnd + 1);
218
89
  if (!isValidETag(etag)) return null;
219
- const catalog = parseValidatedCatalog(body, expectedScope);
90
+ const catalog = parseReleaseCatalog(body, expectedScope);
220
91
  if (catalog === null) return null;
221
92
  const parsed = { catalog, etag, partition, serialized: value };
222
93
  lastParsedCache = parsed;
@@ -307,7 +178,7 @@ const consumeSuccessfulResponse = async (
307
178
  }
308
179
 
309
180
  const body = await response.text();
310
- const catalog = parseValidatedCatalog(body, input.expectedScope);
181
+ const catalog = parseReleaseCatalog(body, input.expectedScope);
311
182
  if (catalog === null) {
312
183
  await removeNativeReleaseCatalogCache(partition);
313
184
  throw new InvalidUpdateResponseError("Received an invalid Release catalog");
package/src/sdkVersion.ts CHANGED
@@ -1 +1 @@
1
- export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.17";
1
+ export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.21";
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type HotUpdaterBaseURL = string | (() => string | Promise<string>);
1
+ export type { HotUpdaterBaseURL } from "@hot-updater/protocol";
2
2
 
3
3
  /**
4
4
  * Information about a signature verification failure.
package/src/wrap.types.ts CHANGED
@@ -29,8 +29,8 @@ export type HotUpdaterFallbackComponentProps = {
29
29
 
30
30
  interface CommonHotUpdaterOptions {
31
31
  /**
32
- * Client plugins, such as `insights()` from
33
- * `@hot-updater/react-native/plugins/insights`. Each plugin's `setup` runs
32
+ * Client plugins, such as `insights()` from `@hot-updater/react-native`.
33
+ * Each plugin's `setup` runs
34
34
  * once, and its hooks observe launches, update checks, downloads, and
35
35
  * update failures. Plugin ids must be unique.
36
36
  */
@@ -1,46 +0,0 @@
1
- package com.hotupdater
2
-
3
- import org.junit.Assert.assertArrayEquals
4
- import org.junit.Assert.assertFalse
5
- import org.junit.Assert.assertThrows
6
- import org.junit.Assert.assertTrue
7
- import org.junit.Rule
8
- import org.junit.Test
9
- import org.junit.rules.TemporaryFolder
10
- import java.util.Base64
11
-
12
- class BsdiffPatchTest {
13
- @get:Rule
14
- val temporaryFolder = TemporaryFolder()
15
-
16
- @Test
17
- fun `applies bsdiff patch and verifies patched output hash`() {
18
- val base = "console.log(\"base bundle\");\n".toByteArray()
19
- val expected = "console.log(\"patched bundle\");\n".toByteArray()
20
- val patch = Base64.getDecoder().decode(BSDIFF_PATCH_FIXTURE_BASE64)
21
- val baseFile = temporaryFolder.newFile("base.bundle").apply { writeBytes(base) }
22
- val patchFile = temporaryFolder.newFile("patch.bsdiff").apply { writeBytes(patch) }
23
- val outputFile = temporaryFolder.newFile("output.bundle")
24
-
25
- BsdiffPatch.apply(baseFile, patchFile, outputFile)
26
-
27
- assertArrayEquals(expected, outputFile.readBytes())
28
- assertTrue(HashUtils.verifyHash(outputFile, HashUtils.calculateSHA256(outputFile)))
29
- assertFalse(HashUtils.verifyHash(outputFile, HashUtils.calculateSHA256(baseFile)))
30
- }
31
-
32
- @Test
33
- fun `rejects invalid bsdiff patch`() {
34
- val base = "console.log(\"base bundle\");\n".toByteArray()
35
- val invalidPatch = "not-a-bsdiff-patch-with-header".toByteArray()
36
-
37
- assertThrows(IllegalArgumentException::class.java) {
38
- BsdiffPatch.apply(base, invalidPatch)
39
- }
40
- }
41
-
42
- companion object {
43
- const val BSDIFF_PATCH_FIXTURE_BASE64 =
44
- "RU5EU0xFWS9CU0RJRkY0Mx8AAAAAAAAAQlpoOTFBWSZTWb12MIEAAAB5gEQYAADQYQAIPsXOACAAIo0A0NAaNCgAGgZMgHAtYscVxxRtTt4nmaj70g4gQSF5+T4u5IpwoSF67GEC"
45
- }
46
- }