@appsonair.ir/react-native 1.0.5 → 1.0.7

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.
@@ -66,5 +66,8 @@ dependencies {
66
66
  implementation "com.facebook.react:react-android"
67
67
  implementation "org.jetbrains.kotlin:kotlin-stdlib"
68
68
  implementation "androidx.core:core:1.13.1"
69
- implementation "com.google.firebase:firebase-messaging:24.1.1"
69
+ // Use BOM so transitive artifacts (installations, play-services-*) resolve
70
+ // correctly and stay aligned with @react-native-firebase when present.
71
+ implementation platform("com.google.firebase:firebase-bom:33.9.0")
72
+ implementation "com.google.firebase:firebase-messaging"
70
73
  }
@@ -56,7 +56,10 @@ internal object OTAFcmHelper {
56
56
  ensureNotificationChannel(context.applicationContext)
57
57
 
58
58
  try {
59
- FirebaseMessaging.getInstance(app).token.addOnCompleteListener { task ->
59
+ // getInstance(FirebaseApp) is package-private in newer Firebase Messaging SDKs;
60
+ // FirebaseApp.get(Class) is the supported public API for named apps.
61
+ val messaging = app.get(FirebaseMessaging::class.java)
62
+ messaging.token.addOnCompleteListener { task ->
60
63
  val token =
61
64
  if (task.isSuccessful) {
62
65
  task.result?.takeIf { it.isNotBlank() }
package/lib/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import { configureFromProject, getDeploymentKey, isOtaConfigured, resolveUpdater
3
3
  import { sync, restartApp, installApk, resumePendingApkInstall, clearUpdate, getInstalledBundlePath, notifyAppReady } from './sync';
4
4
  import { logEvent } from './events';
5
5
  import { ping, startTelemetry, stopTelemetry } from './telemetry';
6
+ import { clearUserInfo, getUserInfo, setUserInfo } from './userInfo';
6
7
  import { UpdateCheckResult } from './types';
7
8
  export { configure, getConfig, getServerUrl } from './config';
8
9
  export { DEFAULT_SERVER_URL } from './constants';
@@ -13,6 +14,8 @@ export declare function checkUpdate(): Promise<UpdateCheckResult>;
13
14
  export { sync, restartApp, installApk, resumePendingApkInstall, clearUpdate, getInstalledBundlePath, notifyAppReady, isSyncInProgress, respondToUpdatePrompt, } from './sync';
14
15
  export { ping, startTelemetry, stopTelemetry } from './telemetry';
15
16
  export { logEvent } from './events';
17
+ export { setUserInfo, clearUserInfo, getUserInfo } from './userInfo';
18
+ export type { OTAUserInfo } from './userInfo';
16
19
  export { OTAUpdateModal } from './ui/OTAUpdateModal';
17
20
  export { OTAUpdateProvider } from './ui/OTAUpdateProvider';
18
21
  export declare const OTAUpdater: {
@@ -31,5 +34,8 @@ export declare const OTAUpdater: {
31
34
  startTelemetry: typeof startTelemetry;
32
35
  stopTelemetry: typeof stopTelemetry;
33
36
  logEvent: typeof logEvent;
37
+ setUserInfo: typeof setUserInfo;
38
+ clearUserInfo: typeof clearUserInfo;
39
+ getUserInfo: typeof getUserInfo;
34
40
  };
35
41
  export * from './types';
package/lib/index.js CHANGED
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.OTAUpdater = exports.OTAUpdateProvider = exports.OTAUpdateModal = exports.logEvent = exports.stopTelemetry = exports.startTelemetry = exports.ping = exports.respondToUpdatePrompt = exports.isSyncInProgress = exports.notifyAppReady = exports.getInstalledBundlePath = exports.clearUpdate = exports.resumePendingApkInstall = exports.installApk = exports.restartApp = exports.sync = exports.resolveUpdaterConfig = exports.isOtaConfigured = exports.getDeploymentKey = exports.configureFromProject = exports.DEFAULT_SERVER_URL = exports.getServerUrl = exports.getConfig = exports.configure = void 0;
20
+ exports.OTAUpdater = exports.OTAUpdateProvider = exports.OTAUpdateModal = exports.getUserInfo = exports.clearUserInfo = exports.setUserInfo = exports.logEvent = exports.stopTelemetry = exports.startTelemetry = exports.ping = exports.respondToUpdatePrompt = exports.isSyncInProgress = exports.notifyAppReady = exports.getInstalledBundlePath = exports.clearUpdate = exports.resumePendingApkInstall = exports.installApk = exports.restartApp = exports.sync = exports.resolveUpdaterConfig = exports.isOtaConfigured = exports.getDeploymentKey = exports.configureFromProject = exports.DEFAULT_SERVER_URL = exports.getServerUrl = exports.getConfig = exports.configure = void 0;
21
21
  exports.getCurrentLabel = getCurrentLabel;
22
22
  exports.checkUpdate = checkUpdate;
23
23
  const react_native_device_info_1 = __importDefault(require("react-native-device-info"));
@@ -31,6 +31,7 @@ Object.defineProperty(exports, "resolveUpdaterConfig", { enumerable: true, get:
31
31
  const sync_1 = require("./sync");
32
32
  const events_1 = require("./events");
33
33
  const telemetry_1 = require("./telemetry");
34
+ const userInfo_1 = require("./userInfo");
34
35
  var config_2 = require("./config");
35
36
  Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return config_2.configure; } });
36
37
  Object.defineProperty(exports, "getConfig", { enumerable: true, get: function () { return config_2.getConfig; } });
@@ -68,6 +69,10 @@ Object.defineProperty(exports, "startTelemetry", { enumerable: true, get: functi
68
69
  Object.defineProperty(exports, "stopTelemetry", { enumerable: true, get: function () { return telemetry_2.stopTelemetry; } });
69
70
  var events_2 = require("./events");
70
71
  Object.defineProperty(exports, "logEvent", { enumerable: true, get: function () { return events_2.logEvent; } });
72
+ var userInfo_2 = require("./userInfo");
73
+ Object.defineProperty(exports, "setUserInfo", { enumerable: true, get: function () { return userInfo_2.setUserInfo; } });
74
+ Object.defineProperty(exports, "clearUserInfo", { enumerable: true, get: function () { return userInfo_2.clearUserInfo; } });
75
+ Object.defineProperty(exports, "getUserInfo", { enumerable: true, get: function () { return userInfo_2.getUserInfo; } });
71
76
  var OTAUpdateModal_1 = require("./ui/OTAUpdateModal");
72
77
  Object.defineProperty(exports, "OTAUpdateModal", { enumerable: true, get: function () { return OTAUpdateModal_1.OTAUpdateModal; } });
73
78
  var OTAUpdateProvider_1 = require("./ui/OTAUpdateProvider");
@@ -88,5 +93,8 @@ exports.OTAUpdater = {
88
93
  startTelemetry: telemetry_1.startTelemetry,
89
94
  stopTelemetry: telemetry_1.stopTelemetry,
90
95
  logEvent: events_1.logEvent,
96
+ setUserInfo: userInfo_1.setUserInfo,
97
+ clearUserInfo: userInfo_1.clearUserInfo,
98
+ getUserInfo: userInfo_1.getUserInfo,
91
99
  };
92
100
  __exportStar(require("./types"), exports);
package/lib/sync.js CHANGED
@@ -456,6 +456,98 @@ function getApkFilePath(label) {
456
456
  const dirs = ReactNativeBlobUtil.fs.dirs;
457
457
  return `${dirs.DocumentDir}/ota-updates/${label}.apk`;
458
458
  }
459
+ function getApkZipPath(label) {
460
+ const ReactNativeBlobUtil = require('react-native-blob-util').default;
461
+ const dirs = ReactNativeBlobUtil.fs.dirs;
462
+ return `${dirs.DocumentDir}/ota-updates/${label}-apk.zip`;
463
+ }
464
+ function getApkExtractDir(label) {
465
+ const ReactNativeBlobUtil = require('react-native-blob-util').default;
466
+ const dirs = ReactNativeBlobUtil.fs.dirs;
467
+ return `${dirs.DocumentDir}/ota-updates/${label}-apk`;
468
+ }
469
+ function apkAbiFromName(fileName) {
470
+ const lower = fileName.replace(/^.*[/\\]/, '').toLowerCase();
471
+ if (lower.includes('arm64-v8a') || lower.includes('arm64') || lower.includes('-v8a')) {
472
+ return 'arm64-v8a';
473
+ }
474
+ if (lower.includes('armeabi-v7a') ||
475
+ lower.includes('armeabi') ||
476
+ lower.includes('-v7a')) {
477
+ return 'armeabi-v7a';
478
+ }
479
+ if (lower.includes('x86_64')) {
480
+ return 'x86_64';
481
+ }
482
+ if (lower.includes('universal')) {
483
+ return 'universal';
484
+ }
485
+ return null;
486
+ }
487
+ async function listFilesRecursive(dir) {
488
+ const ReactNativeBlobUtil = require('react-native-blob-util').default;
489
+ const entries = await ReactNativeBlobUtil.fs.ls(dir);
490
+ const out = [];
491
+ for (const entry of entries) {
492
+ const path = `${dir}/${entry}`;
493
+ try {
494
+ const entryStat = await ReactNativeBlobUtil.fs.stat(path);
495
+ if (entryStat.type === 'directory') {
496
+ out.push(...(await listFilesRecursive(path)));
497
+ }
498
+ else {
499
+ out.push(path);
500
+ }
501
+ }
502
+ catch {
503
+ out.push(path);
504
+ }
505
+ }
506
+ return out;
507
+ }
508
+ async function pickExtractedApk(dir, deviceAbi) {
509
+ const files = await listFilesRecursive(dir);
510
+ const apks = files.filter((path) => path.toLowerCase().endsWith('.apk'));
511
+ if (apks.length === 0) {
512
+ throw new Error(`No APK found after unzipping ${dir}`);
513
+ }
514
+ if (apks.length === 1) {
515
+ return apks[0];
516
+ }
517
+ const wanted = (deviceAbi ?? (await getDeviceAbi()) ?? '').toLowerCase();
518
+ const scored = apks.map((path) => {
519
+ const abi = apkAbiFromName(path);
520
+ let score = 0;
521
+ if (abi && wanted && abi === wanted) {
522
+ score = 3;
523
+ }
524
+ else if (wanted.includes('arm64') && abi === 'arm64-v8a') {
525
+ score = 3;
526
+ }
527
+ else if ((wanted.includes('v7') || wanted.includes('armeabi')) &&
528
+ abi === 'armeabi-v7a') {
529
+ score = 2;
530
+ }
531
+ else if (abi === 'universal') {
532
+ score = 1;
533
+ }
534
+ return { path, score };
535
+ });
536
+ scored.sort((a, b) => b.score - a.score);
537
+ return scored[0].path;
538
+ }
539
+ async function extractApkUpdate(label, zipPath, deviceAbi) {
540
+ const ReactNativeBlobUtil = require('react-native-blob-util').default;
541
+ const extractDir = getApkExtractDir(label);
542
+ await removePathRecursive(extractDir);
543
+ await ReactNativeBlobUtil.fs.mkdir(extractDir);
544
+ await (0, zipExtract_1.extractZipToDirectory)(zipPath, extractDir);
545
+ const zipExists = await ReactNativeBlobUtil.fs.exists(zipPath);
546
+ if (zipExists) {
547
+ await ReactNativeBlobUtil.fs.unlink(zipPath);
548
+ }
549
+ return pickExtractedApk(extractDir, deviceAbi);
550
+ }
459
551
  function getBundleDirectory() {
460
552
  const ReactNativeBlobUtil = require('react-native-blob-util').default;
461
553
  return `${ReactNativeBlobUtil.fs.dirs.DocumentDir}/ota-updates`;
@@ -480,10 +572,13 @@ async function cleanupOldUpdates(keepLabel) {
480
572
  keepLabel,
481
573
  `${keepLabel}.zip`,
482
574
  `${keepLabel}.bundle`,
575
+ `${keepLabel}.apk`,
576
+ `${keepLabel}-apk`,
577
+ `${keepLabel}-apk.zip`,
483
578
  ]);
484
579
  const entries = await ReactNativeBlobUtil.fs.ls(dir);
485
580
  await Promise.all(entries.map(async (entry) => {
486
- if (keep.has(entry) || entry.endsWith('.apk')) {
581
+ if (keep.has(entry)) {
487
582
  return;
488
583
  }
489
584
  await removePathRecursive(`${dir}/${entry}`);
@@ -575,7 +670,7 @@ async function cleanupPartialDownload(path) {
575
670
  * bytesWritten !== Content-Length — including when Content-Length is wrong
576
671
  * but the full file actually landed. Recover those cases via size/hash checks.
577
672
  */
578
- async function tryRecoverCompletedDownload(destPath, extension, totalBytes, expectedHash, packageFormat, label) {
673
+ async function tryRecoverCompletedDownload(destPath, extension, totalBytes, expectedHash, packageFormat, label, deviceAbi) {
579
674
  try {
580
675
  const ReactNativeBlobUtil = require('react-native-blob-util').default;
581
676
  const exists = await ReactNativeBlobUtil.fs.exists(destPath);
@@ -594,6 +689,9 @@ async function tryRecoverCompletedDownload(destPath, extension, totalBytes, expe
594
689
  else if (!sizeLooksComplete) {
595
690
  return null;
596
691
  }
692
+ if (extension === 'apk' && (packageFormat === 'zip' || (await isZipArchive(destPath)))) {
693
+ return extractApkUpdate(label, destPath, deviceAbi);
694
+ }
597
695
  if (extension === 'bundle' && (await shouldExtractZipPackage(destPath, packageFormat))) {
598
696
  return extractBundleUpdate(label, destPath);
599
697
  }
@@ -606,16 +704,22 @@ async function tryRecoverCompletedDownload(destPath, extension, totalBytes, expe
606
704
  async function downloadBundle(downloadUrl, deploymentKey, label, expectedSize, onProgress, extension = 'bundle', deviceAbi, packageFormat = 'bundle', expectedHash) {
607
705
  const ReactNativeBlobUtil = require('react-native-blob-util').default;
608
706
  const isZipBundle = extension === 'bundle' && packageFormat === 'zip';
707
+ const isApkZip = extension === 'apk' && packageFormat === 'zip';
609
708
  const destPath = isZipBundle
610
709
  ? getBundleZipPath(label)
611
- : extension === 'apk'
612
- ? getApkFilePath(label)
613
- : getLegacyBundleFilePath(label);
710
+ : isApkZip
711
+ ? getApkZipPath(label)
712
+ : extension === 'apk'
713
+ ? getApkFilePath(label)
714
+ : getLegacyBundleFilePath(label);
614
715
  const totalBytes = await resolveDownloadSize(downloadUrl, deploymentKey, expectedSize, deviceAbi);
615
716
  await ensureBundleDirectory();
616
717
  if (isZipBundle) {
617
718
  await removePathRecursive(getBundleReleaseDir(label));
618
719
  }
720
+ if (isApkZip) {
721
+ await removePathRecursive(getApkExtractDir(label));
722
+ }
619
723
  let lastError;
620
724
  for (let attempt = 1; attempt <= DOWNLOAD_MAX_ATTEMPTS; attempt++) {
621
725
  await cleanupPartialDownload(destPath);
@@ -676,6 +780,9 @@ async function downloadBundle(downloadUrl, deploymentKey, label, expectedSize, o
676
780
  throw new Error('Package hash verification failed');
677
781
  }
678
782
  }
783
+ if (extension === 'apk' && (isApkZip || (await isZipArchive(destPath)))) {
784
+ return extractApkUpdate(label, destPath, deviceAbi);
785
+ }
679
786
  if (extension === 'bundle' && (await shouldExtractZipPackage(destPath, packageFormat))) {
680
787
  return extractBundleUpdate(label, destPath);
681
788
  }
@@ -684,7 +791,7 @@ async function downloadBundle(downloadUrl, deploymentKey, label, expectedSize, o
684
791
  catch (error) {
685
792
  lastError = error;
686
793
  if (isRetryableDownloadError(error)) {
687
- const recovered = await tryRecoverCompletedDownload(destPath, extension, totalBytes, expectedHash, packageFormat, label);
794
+ const recovered = await tryRecoverCompletedDownload(destPath, extension, totalBytes, expectedHash, packageFormat, label, deviceAbi);
688
795
  if (recovered) {
689
796
  if (onProgress)
690
797
  onProgress(1);
@@ -969,7 +1076,7 @@ async function performApkSync(update, showUI, messages, onProgress, changelog) {
969
1076
  label: update.label,
970
1077
  description: notes,
971
1078
  isMandatory: update.isMandatory,
972
- }), 'apk', update.deviceAbi, 'bundle', update.packageHash);
1079
+ }), 'apk', update.deviceAbi, update.packageFormat ?? 'bundle', update.packageHash);
973
1080
  onProgress?.(types_1.SyncStatus.INSTALLING, 1);
974
1081
  // Drop the OTA JS path before opening the installer so the new APK does not
975
1082
  // keep loading the previous bundle on first launch after install.
package/lib/telemetry.js CHANGED
@@ -11,6 +11,7 @@ const react_native_device_info_1 = __importDefault(require("react-native-device-
11
11
  const config_1 = require("./config");
12
12
  const deviceProfile_1 = require("./deviceProfile");
13
13
  const sync_1 = require("./sync");
14
+ const userInfo_1 = require("./userInfo");
14
15
  const PING_INTERVAL_MS = 60000;
15
16
  let started = false;
16
17
  let pingTimer = null;
@@ -27,6 +28,7 @@ async function buildPingBody(sessionStart) {
27
28
  (0, deviceProfile_1.getDeviceProfile)(),
28
29
  (0, sync_1.getFcmToken)(),
29
30
  ]);
31
+ const userInfo = (0, userInfo_1.getUserInfo)();
30
32
  return {
31
33
  clientUniqueId,
32
34
  appVersion: react_native_device_info_1.default.getVersion(),
@@ -36,6 +38,7 @@ async function buildPingBody(sessionStart) {
36
38
  packageHash: meta?.packageHash,
37
39
  fcmToken,
38
40
  sessionStart,
41
+ ...(userInfo ?? {}),
39
42
  };
40
43
  }
41
44
  async function ping(options = {}) {
@@ -0,0 +1,18 @@
1
+ export interface OTAUserInfo {
2
+ /** Host-app user id (string or number coerced to string). */
3
+ userId?: string | number | null;
4
+ userName?: string | null;
5
+ userEmail?: string | null;
6
+ userPhone?: string | null;
7
+ }
8
+ export type SanitizedOTAUserInfo = {
9
+ userId?: string;
10
+ userName?: string;
11
+ userEmail?: string;
12
+ userPhone?: string;
13
+ };
14
+ export declare function sanitizeUserInfo(info: OTAUserInfo | null | undefined): SanitizedOTAUserInfo | null;
15
+ /** Attach logged-in host-app user to subsequent device pings. Pass null to clear. */
16
+ export declare function setUserInfo(info: OTAUserInfo | null): void;
17
+ export declare function clearUserInfo(): void;
18
+ export declare function getUserInfo(): SanitizedOTAUserInfo | null;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.sanitizeUserInfo = sanitizeUserInfo;
37
+ exports.setUserInfo = setUserInfo;
38
+ exports.clearUserInfo = clearUserInfo;
39
+ exports.getUserInfo = getUserInfo;
40
+ let currentUserInfo = null;
41
+ function sanitizeField(value, maxLen) {
42
+ if (value == null) {
43
+ return undefined;
44
+ }
45
+ const text = String(value).trim();
46
+ if (!text) {
47
+ return undefined;
48
+ }
49
+ return text.slice(0, maxLen);
50
+ }
51
+ function sanitizeUserInfo(info) {
52
+ if (!info || typeof info !== 'object') {
53
+ return null;
54
+ }
55
+ const sanitized = {};
56
+ const userId = sanitizeField(info.userId, 128);
57
+ const userName = sanitizeField(info.userName, 128);
58
+ const userEmail = sanitizeField(info.userEmail, 256);
59
+ const userPhone = sanitizeField(info.userPhone, 64);
60
+ if (userId)
61
+ sanitized.userId = userId;
62
+ if (userName)
63
+ sanitized.userName = userName;
64
+ if (userEmail)
65
+ sanitized.userEmail = userEmail;
66
+ if (userPhone)
67
+ sanitized.userPhone = userPhone;
68
+ return Object.keys(sanitized).length > 0 ? sanitized : null;
69
+ }
70
+ /** Attach logged-in host-app user to subsequent device pings. Pass null to clear. */
71
+ function setUserInfo(info) {
72
+ currentUserInfo = sanitizeUserInfo(info);
73
+ // Best-effort immediate sync so dashboards update without waiting for the interval.
74
+ void Promise.resolve().then(() => __importStar(require('./telemetry'))).then((mod) => mod.ping())
75
+ .catch(() => undefined);
76
+ }
77
+ function clearUserInfo() {
78
+ currentUserInfo = null;
79
+ void Promise.resolve().then(() => __importStar(require('./telemetry'))).then((mod) => mod.ping())
80
+ .catch(() => undefined);
81
+ }
82
+ function getUserInfo() {
83
+ return currentUserInfo;
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsonair.ir/react-native",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "React Native client SDK for self-hosted OTA updates",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",