react-native-update 10.55.1 → 10.56.1
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/README-CN.md +35 -0
- package/README.md +37 -0
- package/android/build.gradle +53 -27
- package/android/jni/Application.mk +6 -1
- package/android/jni/hpatch.c +59 -4
- 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/proguard.pro +70 -14
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +15 -8
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +1 -0
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +1 -2
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +25 -0
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +98 -34
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +29 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +15 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +59 -0
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +27 -1
- package/android/src/main/java/cn/reactnative/modules/update/NativeCheckOrchestrator.java +57 -16
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +38 -46
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +279 -183
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +14 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +222 -215
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +5 -0
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +23 -25
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +29 -29
- package/cpp/patch_core/archive_limits.h +6 -0
- package/cpp/patch_core/jni_util.h +6 -2
- package/cpp/patch_core/patch_core.cpp +237 -84
- package/cpp/patch_core/patch_core.h +11 -0
- package/cpp/patch_core/patch_core_android.cpp +49 -33
- package/cpp/patch_core/update_core_android.cpp +134 -103
- package/cpp/update_flow_core/flow_json.cpp +150 -17
- package/cpp/update_flow_core/flow_json.h +13 -1
- package/cpp/update_flow_core/update_flow_core.cpp +78 -8
- package/cpp/update_flow_core/update_flow_core.h +9 -0
- package/cpp/update_flow_core/update_flow_jni.cpp +44 -25
- package/harmony/hvigor-plugin.ts +168 -21
- package/harmony/pushy/src/README.md +74 -0
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +13 -3
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +11 -5
- package/harmony/pushy/src/main/cpp/pushy.cpp +103 -18
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +61 -5
- package/harmony/pushy/src/main/ets/DownloadTask.ts +255 -166
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +75 -0
- package/harmony/pushy/src/main/ets/EventHub.ts +22 -20
- package/harmony/pushy/src/main/ets/InstallRecord.ts +79 -21
- package/harmony/pushy/src/main/ets/Logger.ts +42 -16
- package/harmony/pushy/src/main/ets/NativeCheckOrchestrator.ts +98 -17
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +10 -3
- package/harmony/pushy/src/main/ets/PathUtils.ts +9 -3
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +139 -53
- package/harmony/pushy/src/main/ets/UpdateContext.ts +269 -159
- package/harmony/pushy/src/main/module.json5 +5 -1
- package/harmony/pushy.har +0 -0
- package/ios/PrivacyInfo.xcprivacy +79 -0
- package/ios/RCTPushy/RCTPushy.mm +399 -170
- package/ios/RCTPushy/RCTPushyDownloader.h +14 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +136 -11
- package/package.json +14 -24
- package/react-native-update.podspec +43 -3
- package/src/client.ts +236 -82
- package/src/context.ts +40 -27
- package/src/core.ts +41 -16
- package/src/endpoint.ts +2 -1
- package/src/error.ts +49 -11
- package/src/index.ts +14 -0
- package/src/locales/en.ts +1 -23
- package/src/locales/zh.ts +1 -22
- package/src/provider.tsx +159 -111
- package/src/type.ts +0 -2
- package/src/updateFlowCore.ts +10 -2
- package/src/utils.ts +61 -4
- package/android/src/main/java/cn/reactnative/modules/update/ReactNativeHostHandler.java +0 -13
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +0 -45
|
@@ -13,24 +13,53 @@ import {
|
|
|
13
13
|
markJsCheckCompleted,
|
|
14
14
|
KEY_RESP_CACHE,
|
|
15
15
|
} from './NativeCheckOrchestrator';
|
|
16
|
+
import {
|
|
17
|
+
ERROR_FILE_OPERATION_FAILED,
|
|
18
|
+
ERROR_INVALID_HASH_INFO,
|
|
19
|
+
ERROR_INVALID_OPTIONS,
|
|
20
|
+
ERROR_MARK_SUCCESS_FAILED,
|
|
21
|
+
ERROR_RESET_FAILED,
|
|
22
|
+
ERROR_RESTART_FAILED,
|
|
23
|
+
ERROR_SWITCH_VERSION_FAILED,
|
|
24
|
+
ERROR_UNSUPPORTED_PLATFORM,
|
|
25
|
+
createUpdateError,
|
|
26
|
+
getErrorMessage,
|
|
27
|
+
toUpdateError,
|
|
28
|
+
} from './ErrorCodes';
|
|
29
|
+
|
|
30
|
+
export { getErrorMessage } from './ErrorCodes';
|
|
16
31
|
|
|
17
32
|
const TAG = 'PushyTurboModule';
|
|
18
33
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
interface RestartWant {
|
|
35
|
+
bundleName: string;
|
|
36
|
+
abilityName?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// applicationContext.restartApp 是 API 12 才有的可选能力,按结构探测。
|
|
40
|
+
interface RestartableApplicationContext {
|
|
41
|
+
restartApp?: (want: RestartWant) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ReloadEventEmitter {
|
|
45
|
+
emit(event: string, payload: Object): void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// RNOH 的 devToolsController 不在公开的 UITurboModuleContext 类型里。
|
|
49
|
+
interface DevToolsControllerHolder {
|
|
50
|
+
devToolsController?: { eventEmitter: ReloadEventEmitter };
|
|
24
51
|
}
|
|
25
52
|
|
|
26
53
|
export function validateHashInfo(info: string): void {
|
|
54
|
+
let valid = false;
|
|
27
55
|
try {
|
|
28
|
-
const parsed = JSON.parse(info);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
56
|
+
const parsed = JSON.parse(info) as Object | null;
|
|
57
|
+
valid = !!parsed && typeof parsed === 'object' && !Array.isArray(parsed);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
valid = false;
|
|
60
|
+
}
|
|
61
|
+
if (!valid) {
|
|
62
|
+
throw createUpdateError(ERROR_INVALID_HASH_INFO, 'invalid json string');
|
|
34
63
|
}
|
|
35
64
|
}
|
|
36
65
|
|
|
@@ -42,36 +71,61 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
42
71
|
|
|
43
72
|
constructor(protected ctx: UITurboModuleContext) {
|
|
44
73
|
super(ctx);
|
|
74
|
+
if (ctx.isDebugModeEnabled) {
|
|
75
|
+
// 宿主处于 RN 调试模式:打开 debug 级日志,方便接入排查。
|
|
76
|
+
logger.setDebug(true);
|
|
77
|
+
}
|
|
45
78
|
logger.debug(TAG, ',PushyTurboModule constructor');
|
|
46
79
|
this.mUiCtx = ctx.uiAbilityContext;
|
|
47
|
-
this.context = UpdateContext.getInstance(
|
|
80
|
+
this.context = UpdateContext.getInstance(
|
|
81
|
+
this.mUiCtx,
|
|
82
|
+
ctx.isDebugModeEnabled,
|
|
83
|
+
);
|
|
48
84
|
EventHub.getInstance().setRNInstance(ctx.rnInstance);
|
|
49
85
|
}
|
|
50
86
|
|
|
87
|
+
// RNOH 销毁 TurboModule 时的钩子:释放对已销毁 RNInstance 的引用(只在
|
|
88
|
+
// EventHub 仍指向它时,见 EventHub.clearRNInstance)。
|
|
89
|
+
__onDestroy__(): void {
|
|
90
|
+
EventHub.getInstance().clearRNInstance(this.ctx.rnInstance);
|
|
91
|
+
}
|
|
92
|
+
|
|
51
93
|
private getBundleFlags(): bundleManager.BundleFlag {
|
|
52
94
|
return bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
|
|
53
95
|
}
|
|
54
96
|
|
|
55
97
|
private requireHash(hash: string, methodName: string): string {
|
|
56
98
|
if (!hash) {
|
|
57
|
-
throw
|
|
99
|
+
throw createUpdateError(
|
|
100
|
+
ERROR_INVALID_OPTIONS,
|
|
101
|
+
`${methodName}: empty hash`,
|
|
102
|
+
);
|
|
58
103
|
}
|
|
59
104
|
return hash;
|
|
60
105
|
}
|
|
61
106
|
|
|
107
|
+
private softReload(): void {
|
|
108
|
+
const holder = this.ctx as unknown as DevToolsControllerHolder;
|
|
109
|
+
const devToolsController = holder.devToolsController;
|
|
110
|
+
if (devToolsController) {
|
|
111
|
+
devToolsController.eventEmitter.emit('RELOAD', { reason: 'HotReload2' });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
62
115
|
private async restartAbility(): Promise<void> {
|
|
63
116
|
const bundleInfo = await bundleManager.getBundleInfoForSelf(
|
|
64
117
|
this.getBundleFlags(),
|
|
65
118
|
);
|
|
66
|
-
const want = {
|
|
119
|
+
const want: RestartWant = {
|
|
67
120
|
bundleName: bundleInfo.name,
|
|
68
121
|
abilityName: this.mUiCtx.abilityInfo?.name,
|
|
69
122
|
};
|
|
70
123
|
try {
|
|
71
|
-
const applicationContext =
|
|
72
|
-
|
|
124
|
+
const applicationContext =
|
|
125
|
+
this.mUiCtx.getApplicationContext() as unknown as RestartableApplicationContext;
|
|
126
|
+
if (applicationContext && typeof applicationContext.restartApp === 'function') {
|
|
73
127
|
logger.debug(TAG, 'restartAbility via applicationContext.restartApp');
|
|
74
|
-
|
|
128
|
+
applicationContext.restartApp(want);
|
|
75
129
|
return;
|
|
76
130
|
}
|
|
77
131
|
} catch (e) {
|
|
@@ -93,10 +147,7 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
93
147
|
private async reloadBridge(): Promise<void> {
|
|
94
148
|
if (this.ctx.isDebugModeEnabled) {
|
|
95
149
|
logger.debug(TAG, 'reloadBridge via devToolsController RELOAD (debug mode)');
|
|
96
|
-
|
|
97
|
-
if (devToolsController) {
|
|
98
|
-
devToolsController.eventEmitter.emit("RELOAD", { reason: 'HotReload2' });
|
|
99
|
-
}
|
|
150
|
+
this.softReload();
|
|
100
151
|
} else {
|
|
101
152
|
logger.debug(TAG, 'reloadBridge via restartAbility (release mode)');
|
|
102
153
|
// If the process truly restarts, this timer dies with it. It only fires
|
|
@@ -107,10 +158,7 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
107
158
|
// path, only in the catch branch where the soft reload runs immediately.
|
|
108
159
|
const fallbackTimer = setTimeout(() => {
|
|
109
160
|
logger.warn(TAG, 'restartAbility did not restart the app within 1.5s, triggering soft reload fallback');
|
|
110
|
-
|
|
111
|
-
if (devToolsController) {
|
|
112
|
-
devToolsController.eventEmitter.emit("RELOAD", { reason: 'HotReload2' });
|
|
113
|
-
}
|
|
161
|
+
this.softReload();
|
|
114
162
|
}, 1500);
|
|
115
163
|
|
|
116
164
|
try {
|
|
@@ -118,10 +166,7 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
118
166
|
} catch (error) {
|
|
119
167
|
clearTimeout(fallbackTimer);
|
|
120
168
|
logger.error(TAG, `restartAbility failed: ${getErrorMessage(error)}, triggering soft reload fallback`);
|
|
121
|
-
|
|
122
|
-
if (devToolsController) {
|
|
123
|
-
devToolsController.eventEmitter.emit("RELOAD", { reason: 'HotReload2' });
|
|
124
|
-
}
|
|
169
|
+
this.softReload();
|
|
125
170
|
}
|
|
126
171
|
}
|
|
127
172
|
}
|
|
@@ -175,22 +220,41 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
175
220
|
return result;
|
|
176
221
|
}
|
|
177
222
|
|
|
178
|
-
|
|
223
|
+
// 以下方法在 JS spec(src/NativePushy.ts)里都返回 Promise,C++ 方法表
|
|
224
|
+
// (PushyTurboModule.cpp)相应用 PUSHY_ASYNC_METHOD 注册:同步注册会把
|
|
225
|
+
// ArkTS 的 Promise 变成空对象、把 reject 丢成 ArkTS 侧的 unhandled rejection。
|
|
226
|
+
// 每个拒绝都带稳定错误码(ErrorCodes.ts):`code` 属性 + `[CODE] ` 消息前缀。
|
|
227
|
+
|
|
228
|
+
async setLocalHashInfo(hash: string, info: string): Promise<void> {
|
|
179
229
|
logger.debug(TAG, ',call setLocalHashInfo');
|
|
230
|
+
this.requireHash(hash, 'setLocalHashInfo');
|
|
180
231
|
validateHashInfo(info);
|
|
181
|
-
|
|
182
|
-
|
|
232
|
+
try {
|
|
233
|
+
await this.context.setKv(`hash_${hash}`, info);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
throw toUpdateError(error, ERROR_FILE_OPERATION_FAILED);
|
|
236
|
+
}
|
|
183
237
|
}
|
|
184
238
|
|
|
185
|
-
|
|
239
|
+
// 没有记录时返回 ''(iOS 同样 resolve nil):JS 侧把空值当 {}。只有存在
|
|
240
|
+
// 但不是 JSON 对象的记录才是 INVALID_HASH_INFO。
|
|
241
|
+
async getLocalHashInfo(hash: string): Promise<string> {
|
|
242
|
+
this.requireHash(hash, 'getLocalHashInfo');
|
|
186
243
|
const value = this.context.getKv(`hash_${hash}`);
|
|
244
|
+
if (!value) {
|
|
245
|
+
return '';
|
|
246
|
+
}
|
|
187
247
|
validateHashInfo(value);
|
|
188
248
|
return value;
|
|
189
249
|
}
|
|
190
250
|
|
|
191
251
|
async setUuid(uuid: string): Promise<void> {
|
|
192
252
|
logger.debug(TAG, ',call setUuid');
|
|
193
|
-
|
|
253
|
+
try {
|
|
254
|
+
await this.context.setKv('uuid', uuid);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
throw toUpdateError(error, ERROR_FILE_OPERATION_FAILED);
|
|
257
|
+
}
|
|
194
258
|
}
|
|
195
259
|
|
|
196
260
|
// Provisioning for the native cold-start update check
|
|
@@ -200,15 +264,29 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
200
264
|
// native check forever with no signal.
|
|
201
265
|
async syncNativeConfig(config: string): Promise<void> {
|
|
202
266
|
logger.debug(TAG, ',call syncNativeConfig');
|
|
203
|
-
|
|
204
|
-
|
|
267
|
+
try {
|
|
268
|
+
JSON.parse(config);
|
|
269
|
+
} catch (e) {
|
|
270
|
+
throw createUpdateError(
|
|
271
|
+
ERROR_INVALID_OPTIONS,
|
|
272
|
+
`syncNativeConfig: config is not valid JSON: ${getErrorMessage(e)}`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
await this.context.setKv(KEY_CONFIG, config);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
throw toUpdateError(error, ERROR_FILE_OPERATION_FAILED);
|
|
279
|
+
}
|
|
205
280
|
}
|
|
206
281
|
|
|
207
282
|
// JS 本进程已拿到有效检查响应:延迟的原生轮次据此跳过重复请求(§10.3)。
|
|
208
283
|
async markJsCheckCompleted(config: string): Promise<void> {
|
|
209
284
|
logger.debug(TAG, ',call markJsCheckCompleted');
|
|
210
285
|
if (typeof config !== 'string' || config.length === 0) {
|
|
211
|
-
throw
|
|
286
|
+
throw createUpdateError(
|
|
287
|
+
ERROR_INVALID_OPTIONS,
|
|
288
|
+
'config must be a non-empty string',
|
|
289
|
+
);
|
|
212
290
|
}
|
|
213
291
|
markJsCheckCompleted(config);
|
|
214
292
|
}
|
|
@@ -224,12 +302,19 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
224
302
|
logger.debug(TAG, ',call reloadUpdate');
|
|
225
303
|
const hash = this.requireHash(options.hash, 'reloadUpdate');
|
|
226
304
|
|
|
305
|
+
// 切换必须真正落盘(switchVersion 内 await flush)后才重启:重启会立刻
|
|
306
|
+
// 杀进程,未落盘的切换就是"重启回旧 bundle"。
|
|
307
|
+
try {
|
|
308
|
+
await this.context.switchVersion(hash);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
logger.error(TAG, `reloadUpdate switch failed: ${getErrorMessage(error)}`);
|
|
311
|
+
throw toUpdateError(error, ERROR_SWITCH_VERSION_FAILED);
|
|
312
|
+
}
|
|
227
313
|
try {
|
|
228
|
-
this.context.switchVersion(hash);
|
|
229
314
|
await this.reloadBridge();
|
|
230
315
|
} catch (error) {
|
|
231
|
-
logger.error(TAG, `reloadUpdate failed: ${getErrorMessage(error)}`);
|
|
232
|
-
throw
|
|
316
|
+
logger.error(TAG, `reloadUpdate restart failed: ${getErrorMessage(error)}`);
|
|
317
|
+
throw toUpdateError(error, ERROR_RESTART_FAILED);
|
|
233
318
|
}
|
|
234
319
|
}
|
|
235
320
|
|
|
@@ -239,38 +324,36 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
239
324
|
await this.reloadBridge();
|
|
240
325
|
} catch (error) {
|
|
241
326
|
logger.error(TAG, `restartApp failed: ${getErrorMessage(error)}`);
|
|
242
|
-
throw
|
|
327
|
+
throw toUpdateError(error, ERROR_RESTART_FAILED);
|
|
243
328
|
}
|
|
244
329
|
}
|
|
245
330
|
|
|
246
|
-
async setNeedUpdate(options: { hash: string }): Promise<
|
|
331
|
+
async setNeedUpdate(options: { hash: string }): Promise<void> {
|
|
247
332
|
logger.debug(TAG, ',call setNeedUpdate');
|
|
248
333
|
const hash = this.requireHash(options.hash, 'setNeedUpdate');
|
|
249
334
|
|
|
250
335
|
try {
|
|
251
|
-
this.context.switchVersion(hash);
|
|
252
|
-
return true;
|
|
336
|
+
await this.context.switchVersion(hash);
|
|
253
337
|
} catch (error) {
|
|
254
338
|
logger.error(TAG, `setNeedUpdate failed: ${getErrorMessage(error)}`);
|
|
255
|
-
throw
|
|
339
|
+
throw toUpdateError(error, ERROR_SWITCH_VERSION_FAILED);
|
|
256
340
|
}
|
|
257
341
|
}
|
|
258
342
|
|
|
259
|
-
async markSuccess(): Promise<
|
|
343
|
+
async markSuccess(): Promise<void> {
|
|
260
344
|
logger.debug(TAG, ',call markSuccess');
|
|
261
345
|
try {
|
|
262
|
-
this.context.markSuccess();
|
|
263
|
-
return true;
|
|
346
|
+
await this.context.markSuccess();
|
|
264
347
|
} catch (error) {
|
|
265
348
|
logger.error(TAG, `markSuccess failed: ${getErrorMessage(error)}`);
|
|
266
|
-
throw error;
|
|
349
|
+
throw toUpdateError(error, ERROR_MARK_SUCCESS_FAILED);
|
|
267
350
|
}
|
|
268
351
|
}
|
|
269
352
|
|
|
270
353
|
async getBundleHash(): Promise<string> {
|
|
271
354
|
logger.debug(TAG, ',call getBundleHash');
|
|
272
355
|
try {
|
|
273
|
-
return this.context.getBundleHash();
|
|
356
|
+
return await this.context.getBundleHash();
|
|
274
357
|
} catch (error) {
|
|
275
358
|
// 空串 = "未知",服务端回退 buildTime 启发式;该方法永不 reject。
|
|
276
359
|
logger.error(TAG, `getBundleHash failed: ${getErrorMessage(error)}`);
|
|
@@ -281,10 +364,10 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
281
364
|
async resetToPackagedBundle(): Promise<void> {
|
|
282
365
|
logger.debug(TAG, ',call resetToPackagedBundle');
|
|
283
366
|
try {
|
|
284
|
-
this.context.resetToPackagedBundle();
|
|
367
|
+
await this.context.resetToPackagedBundle();
|
|
285
368
|
} catch (error) {
|
|
286
369
|
logger.error(TAG, `resetToPackagedBundle failed: ${getErrorMessage(error)}`);
|
|
287
|
-
throw
|
|
370
|
+
throw toUpdateError(error, ERROR_RESET_FAILED);
|
|
288
371
|
}
|
|
289
372
|
}
|
|
290
373
|
|
|
@@ -326,7 +409,10 @@ export class PushyTurboModule extends UITurboModule {
|
|
|
326
409
|
hash: string;
|
|
327
410
|
}): Promise<void> {
|
|
328
411
|
logger.debug(TAG, ',call downloadAndInstallApk');
|
|
329
|
-
throw
|
|
412
|
+
throw createUpdateError(
|
|
413
|
+
ERROR_UNSUPPORTED_PLATFORM,
|
|
414
|
+
'downloadAndInstallApk is only supported on Android',
|
|
415
|
+
);
|
|
330
416
|
}
|
|
331
417
|
|
|
332
418
|
addListener(_eventName: string): void {
|