@scalebun/react-native 1.4.0 → 1.6.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/README.md +33 -0
- package/android/src/main/java/com/scalebun/rn/ota/BsPatch.kt +182 -0
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +88 -13
- package/android/src/oldarch/java/com/scalebun/rn/crash/ScaleBunCrashSpec.kt +16 -0
- package/bin/lib/androidCodemod.js +370 -0
- package/bin/scalebun.js +305 -13
- package/dist/scalebun.full.js +129 -97
- package/dist/scalebun.full.js.map +1 -1
- package/dist/scalebun.slim.js +129 -97
- package/dist/scalebun.slim.js.map +1 -1
- package/ios/Ota/BsPatch.swift +180 -0
- package/ios/Ota/OtaSlotManager.swift +59 -2
- package/ios/Ota/ScaleBunOtaModule.swift +89 -10
- package/lib/commonjs/analytics/EventTracker.js +21 -1
- package/lib/commonjs/analytics/EventTracker.js.map +1 -1
- package/lib/commonjs/core/constants/version.js +1 -1
- package/lib/commonjs/core/id/installationId.js +55 -0
- package/lib/commonjs/core/id/installationId.js.map +1 -0
- package/lib/commonjs/debug/bootstrap.js +12 -0
- package/lib/commonjs/debug/bootstrap.js.map +1 -1
- package/lib/commonjs/features/ota/OtaOrchestrator.js +2 -30
- package/lib/commonjs/features/ota/OtaOrchestrator.js.map +1 -1
- package/lib/commonjs/features/session/BackendSessionAdapter.js +8 -0
- package/lib/commonjs/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/module/analytics/EventTracker.js +21 -1
- package/lib/module/analytics/EventTracker.js.map +1 -1
- package/lib/module/core/constants/version.js +1 -1
- package/lib/module/core/id/installationId.js +50 -0
- package/lib/module/core/id/installationId.js.map +1 -0
- package/lib/module/debug/bootstrap.js +12 -0
- package/lib/module/debug/bootstrap.js.map +1 -1
- package/lib/module/features/ota/OtaOrchestrator.js +1 -29
- package/lib/module/features/ota/OtaOrchestrator.js.map +1 -1
- package/lib/module/features/session/BackendSessionAdapter.js +8 -0
- package/lib/module/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/typescript/analytics/EventTracker.d.ts.map +1 -1
- package/lib/typescript/core/constants/version.d.ts +1 -1
- package/lib/typescript/core/id/installationId.d.ts +20 -0
- package/lib/typescript/core/id/installationId.d.ts.map +1 -0
- package/lib/typescript/debug/bootstrap.d.ts +18 -0
- package/lib/typescript/debug/bootstrap.d.ts.map +1 -1
- package/lib/typescript/features/ota/OtaOrchestrator.d.ts.map +1 -1
- package/lib/typescript/features/session/BackendSessionAdapter.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/analytics/EventTracker.ts +21 -1
- package/src/core/constants/version.ts +1 -1
- package/src/core/id/installationId.ts +52 -0
- package/src/debug/bootstrap.ts +36 -0
- package/src/features/ota/OtaOrchestrator.ts +1 -30
- package/src/features/session/BackendSessionAdapter.ts +8 -0
package/bin/scalebun.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
const fs = require('fs');
|
|
23
23
|
const path = require('path');
|
|
24
24
|
const codemod = require('./lib/iosCodemod');
|
|
25
|
+
const androidCodemod = require('./lib/androidCodemod');
|
|
25
26
|
|
|
26
27
|
const MIN_BRIDGELESS_RNFIREBASE_MAJOR = 18;
|
|
27
28
|
|
|
@@ -597,6 +598,215 @@ function runInitIos(argv) {
|
|
|
597
598
|
process.exit(0);
|
|
598
599
|
}
|
|
599
600
|
|
|
601
|
+
// ─── `init android` — MainApplication OTA wiring + checklist ──────────────────
|
|
602
|
+
|
|
603
|
+
function findAndroidDir() {
|
|
604
|
+
const androidDir = path.join(CWD, 'android');
|
|
605
|
+
return exists(androidDir) ? androidDir : null;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/** [major, minor] of the installed react-native, or null. */
|
|
609
|
+
function rnMinor() {
|
|
610
|
+
const v = pkgVersion('react-native');
|
|
611
|
+
if (!v) return null;
|
|
612
|
+
const parts = v.split('.').map((n) => parseInt(n, 10));
|
|
613
|
+
return [parts[0] || 0, parts[1] || 0];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Detect Android setup that the codemod cannot apply — the checklist items
|
|
618
|
+
* (Firebase config, gradle plugin, manifest permissions, the New-Arch OTA
|
|
619
|
+
* version gate). Mirrors detectIosCapabilities.
|
|
620
|
+
*/
|
|
621
|
+
function detectAndroidCapabilities(androidDir) {
|
|
622
|
+
const rnVersion = pkgVersion('react-native');
|
|
623
|
+
const minor = rnMinor();
|
|
624
|
+
|
|
625
|
+
const gradleProps = readText(path.join(androidDir, 'gradle.properties')) || '';
|
|
626
|
+
const newArch = /(^|\n)\s*newArchEnabled\s*=\s*true/.test(gradleProps);
|
|
627
|
+
|
|
628
|
+
// Bridgeless ignored getJSBundleFile() until RN 0.76.1 — an OTA update installs
|
|
629
|
+
// and silently never runs on 0.74/0.75/0.76.0 with New Arch on.
|
|
630
|
+
const otaDeadZone =
|
|
631
|
+
newArch &&
|
|
632
|
+
minor != null &&
|
|
633
|
+
minor[0] === 0 &&
|
|
634
|
+
(minor[1] === 74 || minor[1] === 75 || (rnVersion || '').startsWith('0.76.0'));
|
|
635
|
+
|
|
636
|
+
const hasGoogleServicesJson = exists(path.join(androidDir, 'app', 'google-services.json'));
|
|
637
|
+
|
|
638
|
+
const manifestPath = path.join(androidDir, 'app', 'src', 'main', 'AndroidManifest.xml');
|
|
639
|
+
const manifest = readText(manifestPath) || '';
|
|
640
|
+
const hasPostNotifications = /android\.permission\.POST_NOTIFICATIONS/.test(manifest);
|
|
641
|
+
const hasChannelMeta = /default_notification_channel_id/.test(manifest);
|
|
642
|
+
|
|
643
|
+
return {
|
|
644
|
+
rnVersion,
|
|
645
|
+
newArch,
|
|
646
|
+
otaDeadZone,
|
|
647
|
+
hasGoogleServicesJson,
|
|
648
|
+
manifestExists: !!readText(manifestPath),
|
|
649
|
+
hasPostNotifications,
|
|
650
|
+
hasChannelMeta,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function printAndroidChecklist(caps) {
|
|
655
|
+
console.log('');
|
|
656
|
+
console.log(paint('Android setup (verify / apply by hand — cannot be safely automated)', C.bold));
|
|
657
|
+
|
|
658
|
+
if (caps.otaDeadZone) {
|
|
659
|
+
console.log(
|
|
660
|
+
' ' + FAIL() + ' ' +
|
|
661
|
+
paint(
|
|
662
|
+
`React Native ${caps.rnVersion} + New Architecture: OTA updates CANNOT LOAD.`,
|
|
663
|
+
C.red,
|
|
664
|
+
),
|
|
665
|
+
);
|
|
666
|
+
console.log(
|
|
667
|
+
paint(
|
|
668
|
+
' Bridgeless ignored getJSBundleFile() until RN 0.76.1. Upgrade to >= 0.76.1,\n' +
|
|
669
|
+
' or set newArchEnabled=false. Until then updates install but never run.',
|
|
670
|
+
C.dim,
|
|
671
|
+
),
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
line(caps.hasGoogleServicesJson ? OK() : WARN(), 'google-services.json', caps.hasGoogleServicesJson ? 'present' : null);
|
|
676
|
+
line(caps.hasPostNotifications ? OK() : WARN(), 'POST_NOTIFICATIONS perm', caps.hasPostNotifications ? 'declared' : null);
|
|
677
|
+
line(caps.hasChannelMeta ? OK() : WARN(), 'default channel meta', caps.hasChannelMeta ? 'declared' : null);
|
|
678
|
+
|
|
679
|
+
const todo = [];
|
|
680
|
+
if (!caps.hasGoogleServicesJson) {
|
|
681
|
+
todo.push(
|
|
682
|
+
'For push: add ' + paint('android/app/google-services.json', C.cyan) +
|
|
683
|
+
' (Firebase console), apply the Google Services gradle plugin, and add ' +
|
|
684
|
+
paint('@react-native-firebase/messaging', C.cyan) + ' for real tokens.',
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
if (caps.manifestExists && !caps.hasPostNotifications) {
|
|
688
|
+
todo.push(
|
|
689
|
+
'Declare the Android 13+ permission in AndroidManifest.xml: ' +
|
|
690
|
+
paint('<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />', C.cyan),
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
if (caps.manifestExists && !caps.hasChannelMeta) {
|
|
694
|
+
todo.push(
|
|
695
|
+
'Declare a default FCM channel meta-data ' +
|
|
696
|
+
'(com.google.firebase.messaging.default_notification_channel_id) so backgrounded ' +
|
|
697
|
+
'notifications have a channel.',
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (todo.length) {
|
|
702
|
+
console.log('');
|
|
703
|
+
console.log(paint('Push follow-ups (skip if you only use OTA)', C.bold + C.yellow));
|
|
704
|
+
todo.forEach((t, i) => console.log(` ${i + 1}. ${t}`));
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function runInitAndroid(argv) {
|
|
709
|
+
const flags = new Set(argv);
|
|
710
|
+
const isCheck = flags.has('--check');
|
|
711
|
+
const isDryRun = flags.has('--dry-run');
|
|
712
|
+
|
|
713
|
+
const androidDir = findAndroidDir();
|
|
714
|
+
if (!androidDir) {
|
|
715
|
+
console.error(paint('✗ No android/ directory found.', C.red));
|
|
716
|
+
console.error(' Run this from a React Native project root (the folder containing android/).');
|
|
717
|
+
process.exit(2);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const found = androidCodemod.findMainApplication(androidDir, fs, path);
|
|
721
|
+
if (!found) {
|
|
722
|
+
console.error(paint('✗ Could not find MainApplication.kt or .java under android/app/src/main.', C.red));
|
|
723
|
+
console.error(' This project uses a non-standard layout — wire OTA bundle resolution manually.');
|
|
724
|
+
process.exit(2);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const minor = rnMinor();
|
|
728
|
+
const rel = path.relative(CWD, found.path);
|
|
729
|
+
console.log('');
|
|
730
|
+
console.log(paint('ScaleBun init android', C.bold + C.cyan));
|
|
731
|
+
console.log(` MainApplication: ${paint(rel, C.cyan)} (${found.kind})`);
|
|
732
|
+
|
|
733
|
+
// Java: not auto-patched — print clear manual instructions, fail non-zero.
|
|
734
|
+
if (found.kind === 'java') {
|
|
735
|
+
console.log('');
|
|
736
|
+
console.log(androidCodemod.javaInstructions(minor));
|
|
737
|
+
printAndroidChecklist(detectAndroidCapabilities(androidDir));
|
|
738
|
+
process.exit(isCheck ? 1 : 2);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const original = readText(found.path) || '';
|
|
742
|
+
|
|
743
|
+
// --check: verify, never write.
|
|
744
|
+
if (isCheck) {
|
|
745
|
+
const a = androidCodemod.analyzeKotlin(original);
|
|
746
|
+
const wired = androidCodemod.isFullyWiredKotlin(original, minor);
|
|
747
|
+
console.log('');
|
|
748
|
+
console.log(paint('OTA wiring status', C.bold));
|
|
749
|
+
line(a.hasImport ? OK() : FAIL(), 'ScaleBunOtaModule import', a.hasImport ? 'present' : null);
|
|
750
|
+
line(a.hasBundleWiring ? OK() : FAIL(), 'getJSBundleFile wiring', a.hasBundleWiring ? 'present' : null);
|
|
751
|
+
if (androidCodemod.isReactHostEra(minor)) {
|
|
752
|
+
line(a.hasJsBundleFilePathArg ? OK() : FAIL(), 'jsBundleFilePath arg (RN >= 0.82)', a.hasJsBundleFilePathArg ? 'present' : null);
|
|
753
|
+
}
|
|
754
|
+
printAndroidChecklist(detectAndroidCapabilities(androidDir));
|
|
755
|
+
console.log('');
|
|
756
|
+
if (wired) {
|
|
757
|
+
console.log(paint('✓ MainApplication is wired for ScaleBun OTA bundle resolution.', C.green));
|
|
758
|
+
process.exit(0);
|
|
759
|
+
}
|
|
760
|
+
console.log(paint('✗ MainApplication is missing OTA bundle wiring. Run `npx scalebun init android`.', C.red));
|
|
761
|
+
process.exit(1);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const result = androidCodemod.patchKotlin(original, { minor });
|
|
765
|
+
|
|
766
|
+
if (!result.changed) {
|
|
767
|
+
console.log('');
|
|
768
|
+
if (result.manual.length) {
|
|
769
|
+
console.log(paint('Could not auto-apply — wire it by hand:', C.bold + C.yellow));
|
|
770
|
+
result.manual.forEach((m) => console.log(m));
|
|
771
|
+
} else {
|
|
772
|
+
console.log(paint('✓ Already wired — no changes needed (idempotent).', C.green));
|
|
773
|
+
}
|
|
774
|
+
printAndroidChecklist(detectAndroidCapabilities(androidDir));
|
|
775
|
+
process.exit(result.manual.length ? 1 : 0);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
console.log('');
|
|
779
|
+
console.log(paint(isDryRun ? 'Planned changes (--dry-run, nothing written)' : 'Changes', C.bold));
|
|
780
|
+
result.summary.forEach((sline, i) => console.log(` ${i + 1}. ${sline}`));
|
|
781
|
+
if (result.manual.length) {
|
|
782
|
+
console.log('');
|
|
783
|
+
console.log(paint('Could not auto-apply (edit by hand)', C.bold + C.yellow));
|
|
784
|
+
result.manual.forEach((m) => console.log(m));
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (isDryRun) {
|
|
788
|
+
console.log('');
|
|
789
|
+
console.log(paint('Dry run — no files written. Re-run without --dry-run to apply.', C.dim));
|
|
790
|
+
printAndroidChecklist(detectAndroidCapabilities(androidDir));
|
|
791
|
+
process.exit(0);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Apply. Never stage / commit — only edits the working-tree file.
|
|
795
|
+
fs.writeFileSync(found.path, result.source, 'utf8');
|
|
796
|
+
console.log('');
|
|
797
|
+
console.log(paint(`✓ Patched ${rel}`, C.green));
|
|
798
|
+
console.log(paint(' (working-tree edit only — nothing staged or committed)', C.dim));
|
|
799
|
+
|
|
800
|
+
printAndroidChecklist(detectAndroidCapabilities(androidDir));
|
|
801
|
+
|
|
802
|
+
console.log('');
|
|
803
|
+
console.log(paint('Next:', C.bold));
|
|
804
|
+
console.log(' Rebuild the native app (a JS reload does not load the newly-wired native path):');
|
|
805
|
+
console.log(paint(' npx react-native run-android # or your gradle build', C.dim));
|
|
806
|
+
console.log('');
|
|
807
|
+
process.exit(0);
|
|
808
|
+
}
|
|
809
|
+
|
|
600
810
|
// ─── entry ──────────────────────────────────────────────────────────────────
|
|
601
811
|
|
|
602
812
|
function main() {
|
|
@@ -610,9 +820,11 @@ function main() {
|
|
|
610
820
|
const target = argv[1];
|
|
611
821
|
if (target === 'ios') {
|
|
612
822
|
runInitIos(argv.slice(2));
|
|
823
|
+
} else if (target === 'android') {
|
|
824
|
+
runInitAndroid(argv.slice(2));
|
|
613
825
|
} else {
|
|
614
826
|
console.error(`Unknown init target: ${target ?? '(none)'}`);
|
|
615
|
-
console.error('Usage: npx scalebun init ios [--check | --dry-run]');
|
|
827
|
+
console.error('Usage: npx scalebun init <ios|android> [--check | --dry-run]');
|
|
616
828
|
process.exit(2);
|
|
617
829
|
}
|
|
618
830
|
break;
|
|
@@ -621,21 +833,101 @@ function main() {
|
|
|
621
833
|
case 'help':
|
|
622
834
|
case '--help':
|
|
623
835
|
case '-h':
|
|
624
|
-
|
|
625
|
-
console.log(paint('ScaleBun SDK CLI', C.bold));
|
|
626
|
-
console.log('');
|
|
627
|
-
console.log('Usage:');
|
|
628
|
-
console.log(' npx scalebun doctor Diagnose push-notification setup');
|
|
629
|
-
console.log(' npx scalebun init ios Wire the iOS AppDelegate for Direct APNs');
|
|
630
|
-
console.log(' npx scalebun init ios --check Verify hooks exist (exit 1 if missing)');
|
|
631
|
-
console.log(' npx scalebun init ios --dry-run Preview changes without writing');
|
|
632
|
-
console.log('');
|
|
836
|
+
printFullHelp();
|
|
633
837
|
break;
|
|
634
838
|
default:
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
839
|
+
// TWO PACKAGES, ONE COMMAND NAME.
|
|
840
|
+
//
|
|
841
|
+
// The OTA commands (login, ota publish, releases, rollout, quickstart…)
|
|
842
|
+
// live in @scalebun/cli, which declares the SAME `scalebun` bin as this
|
|
843
|
+
// package. Every doc and generated CI pipeline says `npx scalebun ota
|
|
844
|
+
// publish`, so whichever package won the bin race decided whether that
|
|
845
|
+
// worked — and when this one won, a new user's very first command died
|
|
846
|
+
// with "Unknown command: ota" and no way to find out why.
|
|
847
|
+
//
|
|
848
|
+
// Delegate instead of guessing: if @scalebun/cli is installed, run it.
|
|
849
|
+
// If not, say exactly what to install rather than what went wrong.
|
|
850
|
+
delegateToOtaCli(argv);
|
|
638
851
|
}
|
|
639
852
|
}
|
|
640
853
|
|
|
854
|
+
/** Resolve the bundled @scalebun/cli compiled entry, or null when absent. */
|
|
855
|
+
function resolveOtaCli() {
|
|
856
|
+
try {
|
|
857
|
+
return require.resolve('@scalebun/cli/lib/index.js', {
|
|
858
|
+
paths: [process.cwd(), __dirname],
|
|
859
|
+
});
|
|
860
|
+
} catch {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Hand the invocation to @scalebun/cli, preserving argv and exit code.
|
|
867
|
+
* Falls back to an actionable install instruction when it is not present.
|
|
868
|
+
*/
|
|
869
|
+
function delegateToOtaCli(argv) {
|
|
870
|
+
const cliEntry = resolveOtaCli();
|
|
871
|
+
|
|
872
|
+
if (!cliEntry) {
|
|
873
|
+
const cmd = argv[0];
|
|
874
|
+
console.error('');
|
|
875
|
+
console.error(`"${cmd}" is an OTA command, provided by @scalebun/cli.`);
|
|
876
|
+
console.error('');
|
|
877
|
+
console.error('@scalebun/cli ships as a dependency of @scalebun/react-native, so it');
|
|
878
|
+
console.error('should already be present. It could not be resolved — your install may');
|
|
879
|
+
console.error('be incomplete or the dependency was pruned.');
|
|
880
|
+
console.error('');
|
|
881
|
+
console.error('Reinstall dependencies, then re-run:');
|
|
882
|
+
console.error(' npm install');
|
|
883
|
+
console.error(` npx scalebun ${argv.join(' ')}`);
|
|
884
|
+
console.error('');
|
|
885
|
+
console.error('(Or install it explicitly: npm i -D @scalebun/cli)');
|
|
886
|
+
console.error('');
|
|
887
|
+
process.exit(2);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const { spawnSync } = require('child_process');
|
|
891
|
+
const res = spawnSync(process.execPath, [cliEntry, ...argv], {
|
|
892
|
+
stdio: 'inherit',
|
|
893
|
+
});
|
|
894
|
+
process.exit(res.status ?? 1);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Print the full command surface: the SDK-setup commands this package handles
|
|
899
|
+
* directly, followed by the complete @scalebun/cli command tree.
|
|
900
|
+
*
|
|
901
|
+
* The CLI tree is rendered by delegating to `@scalebun/cli --help` rather than a
|
|
902
|
+
* hardcoded list, so it is a single source of truth and cannot drift. When the
|
|
903
|
+
* CLI cannot be resolved (should not happen — it is a dependency) we fall back to
|
|
904
|
+
* a prose note instead of an error, since this is a help screen.
|
|
905
|
+
*/
|
|
906
|
+
function printFullHelp() {
|
|
907
|
+
console.log('');
|
|
908
|
+
console.log(paint('ScaleBun SDK CLI', C.bold));
|
|
909
|
+
console.log('');
|
|
910
|
+
console.log(paint('SDK setup (bundled with @scalebun/react-native):', C.bold));
|
|
911
|
+
console.log(' npx scalebun doctor Diagnose push-notification setup');
|
|
912
|
+
console.log(' npx scalebun init ios Wire the iOS AppDelegate for Direct APNs');
|
|
913
|
+
console.log(' npx scalebun init android Wire Android MainApplication for OTA bundle loading');
|
|
914
|
+
console.log(paint(' add --check to verify (exit 1 if missing) or --dry-run to preview', C.dim));
|
|
915
|
+
console.log('');
|
|
916
|
+
|
|
917
|
+
const cliEntry = resolveOtaCli();
|
|
918
|
+
if (!cliEntry) {
|
|
919
|
+
console.log(paint('OTA & account commands:', C.bold));
|
|
920
|
+
console.log(' Provided by @scalebun/cli (login, quickstart, apps, ota publish,');
|
|
921
|
+
console.log(' releases, rollout…). It ships with this package but could not be');
|
|
922
|
+
console.log(' resolved — run `npm install`, then `npx scalebun --help` again.');
|
|
923
|
+
console.log('');
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
console.log(paint('OTA & account commands (from @scalebun/cli):', C.bold));
|
|
928
|
+
const { spawnSync } = require('child_process');
|
|
929
|
+
spawnSync(process.execPath, [cliEntry, '--help'], { stdio: 'inherit' });
|
|
930
|
+
console.log('');
|
|
931
|
+
}
|
|
932
|
+
|
|
641
933
|
main();
|
package/dist/scalebun.full.js
CHANGED
|
@@ -489,7 +489,7 @@ var SDK_VERSION;
|
|
|
489
489
|
var init_version = __esm({
|
|
490
490
|
"lib/module/core/constants/version.js"() {
|
|
491
491
|
"use strict";
|
|
492
|
-
SDK_VERSION = "1.
|
|
492
|
+
SDK_VERSION = "1.6.0";
|
|
493
493
|
}
|
|
494
494
|
});
|
|
495
495
|
|
|
@@ -10041,6 +10041,10 @@ function enableDebug(config) {
|
|
|
10041
10041
|
__DEV__ && logger.debug("Debug already enabled, skipping");
|
|
10042
10042
|
return;
|
|
10043
10043
|
}
|
|
10044
|
+
if (!__DEV__ && !config.allowInRelease) {
|
|
10045
|
+
logger.warn("[ScaleBun] Debug connection refused in a release build. The debug transport is unencrypted (ws://) and carries captured traffic and the shared secret in cleartext, so it is development-only by default. If you genuinely need it in a release build, set `allowInRelease: true` \u2014 and do not ship that to users.");
|
|
10046
|
+
return;
|
|
10047
|
+
}
|
|
10044
10048
|
if (!isDevToolsBundled()) {
|
|
10045
10049
|
logger.warn("[ScaleBun] debugConnection was configured, but the desktop-debugger tooling is not in this bundle. It is excluded by default (the ScaleBun desktop app is not yet released); opt in with `withScaleBun(config, { features: { devTools: true } })` in metro.config.js. All other SDK behaviour is unaffected.");
|
|
10046
10050
|
return;
|
|
@@ -10550,89 +10554,6 @@ var init_nativeCrashBridge = __esm({
|
|
|
10550
10554
|
}
|
|
10551
10555
|
});
|
|
10552
10556
|
|
|
10553
|
-
// lib/module/analytics/batching.js
|
|
10554
|
-
function chunkEvents(events, size = MAX_BATCH_EVENTS) {
|
|
10555
|
-
const cap = Math.max(1, Math.min(size, MAX_BATCH_EVENTS));
|
|
10556
|
-
if (events.length <= cap) return events.length ? [events.slice()] : [];
|
|
10557
|
-
const out = [];
|
|
10558
|
-
for (let i = 0; i < events.length; i += cap) {
|
|
10559
|
-
out.push(events.slice(i, i + cap));
|
|
10560
|
-
}
|
|
10561
|
-
return out;
|
|
10562
|
-
}
|
|
10563
|
-
function clampBatchSize(configured, fallback) {
|
|
10564
|
-
const n = typeof configured === "number" && configured > 0 ? configured : fallback;
|
|
10565
|
-
return Math.max(1, Math.min(n, MAX_BATCH_EVENTS));
|
|
10566
|
-
}
|
|
10567
|
-
function batchIdempotencyKey(eventIds) {
|
|
10568
|
-
let h = 2166136261;
|
|
10569
|
-
const joined = eventIds.join("");
|
|
10570
|
-
for (let i = 0; i < joined.length; i++) {
|
|
10571
|
-
h ^= joined.charCodeAt(i);
|
|
10572
|
-
h = Math.imul(h, 16777619) >>> 0;
|
|
10573
|
-
}
|
|
10574
|
-
return `sdk-${eventIds.length}-${h.toString(16).padStart(8, "0")}`;
|
|
10575
|
-
}
|
|
10576
|
-
var MAX_BATCH_EVENTS;
|
|
10577
|
-
var init_batching = __esm({
|
|
10578
|
-
"lib/module/analytics/batching.js"() {
|
|
10579
|
-
"use strict";
|
|
10580
|
-
MAX_BATCH_EVENTS = 500;
|
|
10581
|
-
}
|
|
10582
|
-
});
|
|
10583
|
-
|
|
10584
|
-
// lib/module/analytics/automaticEvents.js
|
|
10585
|
-
function compactProperties(properties) {
|
|
10586
|
-
const out = {
|
|
10587
|
-
capture_source: "automatic"
|
|
10588
|
-
};
|
|
10589
|
-
for (const [key, value] of Object.entries(properties ?? {})) {
|
|
10590
|
-
if (value !== void 0) out[key] = value;
|
|
10591
|
-
}
|
|
10592
|
-
return out;
|
|
10593
|
-
}
|
|
10594
|
-
function emitAutomaticEvent(name, properties) {
|
|
10595
|
-
const event = {
|
|
10596
|
-
name,
|
|
10597
|
-
properties: compactProperties(properties)
|
|
10598
|
-
};
|
|
10599
|
-
if (listeners.size === 0) {
|
|
10600
|
-
pending3.push(event);
|
|
10601
|
-
if (pending3.length > MAX_PENDING2) pending3.shift();
|
|
10602
|
-
return;
|
|
10603
|
-
}
|
|
10604
|
-
for (const listener of listeners) {
|
|
10605
|
-
try {
|
|
10606
|
-
listener(event);
|
|
10607
|
-
} catch {
|
|
10608
|
-
}
|
|
10609
|
-
}
|
|
10610
|
-
}
|
|
10611
|
-
function subscribeAutomaticEvents(listener) {
|
|
10612
|
-
listeners.add(listener);
|
|
10613
|
-
if (pending3.length > 0) {
|
|
10614
|
-
const buffered = pending3.splice(0, pending3.length);
|
|
10615
|
-
for (const event of buffered) {
|
|
10616
|
-
try {
|
|
10617
|
-
listener(event);
|
|
10618
|
-
} catch {
|
|
10619
|
-
}
|
|
10620
|
-
}
|
|
10621
|
-
}
|
|
10622
|
-
return () => {
|
|
10623
|
-
listeners.delete(listener);
|
|
10624
|
-
};
|
|
10625
|
-
}
|
|
10626
|
-
var listeners, pending3, MAX_PENDING2;
|
|
10627
|
-
var init_automaticEvents = __esm({
|
|
10628
|
-
"lib/module/analytics/automaticEvents.js"() {
|
|
10629
|
-
"use strict";
|
|
10630
|
-
listeners = /* @__PURE__ */ new Set();
|
|
10631
|
-
pending3 = [];
|
|
10632
|
-
MAX_PENDING2 = 50;
|
|
10633
|
-
}
|
|
10634
|
-
});
|
|
10635
|
-
|
|
10636
10557
|
// lib/module/crypto/hmacSha256.js
|
|
10637
10558
|
function utf8(str3) {
|
|
10638
10559
|
const out = [];
|
|
@@ -10749,6 +10670,89 @@ var init_hmacSha256 = __esm({
|
|
|
10749
10670
|
}
|
|
10750
10671
|
});
|
|
10751
10672
|
|
|
10673
|
+
// lib/module/analytics/batching.js
|
|
10674
|
+
function chunkEvents(events, size = MAX_BATCH_EVENTS) {
|
|
10675
|
+
const cap = Math.max(1, Math.min(size, MAX_BATCH_EVENTS));
|
|
10676
|
+
if (events.length <= cap) return events.length ? [events.slice()] : [];
|
|
10677
|
+
const out = [];
|
|
10678
|
+
for (let i = 0; i < events.length; i += cap) {
|
|
10679
|
+
out.push(events.slice(i, i + cap));
|
|
10680
|
+
}
|
|
10681
|
+
return out;
|
|
10682
|
+
}
|
|
10683
|
+
function clampBatchSize(configured, fallback) {
|
|
10684
|
+
const n = typeof configured === "number" && configured > 0 ? configured : fallback;
|
|
10685
|
+
return Math.max(1, Math.min(n, MAX_BATCH_EVENTS));
|
|
10686
|
+
}
|
|
10687
|
+
function batchIdempotencyKey(eventIds) {
|
|
10688
|
+
let h = 2166136261;
|
|
10689
|
+
const joined = eventIds.join("");
|
|
10690
|
+
for (let i = 0; i < joined.length; i++) {
|
|
10691
|
+
h ^= joined.charCodeAt(i);
|
|
10692
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
10693
|
+
}
|
|
10694
|
+
return `sdk-${eventIds.length}-${h.toString(16).padStart(8, "0")}`;
|
|
10695
|
+
}
|
|
10696
|
+
var MAX_BATCH_EVENTS;
|
|
10697
|
+
var init_batching = __esm({
|
|
10698
|
+
"lib/module/analytics/batching.js"() {
|
|
10699
|
+
"use strict";
|
|
10700
|
+
MAX_BATCH_EVENTS = 500;
|
|
10701
|
+
}
|
|
10702
|
+
});
|
|
10703
|
+
|
|
10704
|
+
// lib/module/analytics/automaticEvents.js
|
|
10705
|
+
function compactProperties(properties) {
|
|
10706
|
+
const out = {
|
|
10707
|
+
capture_source: "automatic"
|
|
10708
|
+
};
|
|
10709
|
+
for (const [key, value] of Object.entries(properties ?? {})) {
|
|
10710
|
+
if (value !== void 0) out[key] = value;
|
|
10711
|
+
}
|
|
10712
|
+
return out;
|
|
10713
|
+
}
|
|
10714
|
+
function emitAutomaticEvent(name, properties) {
|
|
10715
|
+
const event = {
|
|
10716
|
+
name,
|
|
10717
|
+
properties: compactProperties(properties)
|
|
10718
|
+
};
|
|
10719
|
+
if (listeners.size === 0) {
|
|
10720
|
+
pending3.push(event);
|
|
10721
|
+
if (pending3.length > MAX_PENDING2) pending3.shift();
|
|
10722
|
+
return;
|
|
10723
|
+
}
|
|
10724
|
+
for (const listener of listeners) {
|
|
10725
|
+
try {
|
|
10726
|
+
listener(event);
|
|
10727
|
+
} catch {
|
|
10728
|
+
}
|
|
10729
|
+
}
|
|
10730
|
+
}
|
|
10731
|
+
function subscribeAutomaticEvents(listener) {
|
|
10732
|
+
listeners.add(listener);
|
|
10733
|
+
if (pending3.length > 0) {
|
|
10734
|
+
const buffered = pending3.splice(0, pending3.length);
|
|
10735
|
+
for (const event of buffered) {
|
|
10736
|
+
try {
|
|
10737
|
+
listener(event);
|
|
10738
|
+
} catch {
|
|
10739
|
+
}
|
|
10740
|
+
}
|
|
10741
|
+
}
|
|
10742
|
+
return () => {
|
|
10743
|
+
listeners.delete(listener);
|
|
10744
|
+
};
|
|
10745
|
+
}
|
|
10746
|
+
var listeners, pending3, MAX_PENDING2;
|
|
10747
|
+
var init_automaticEvents = __esm({
|
|
10748
|
+
"lib/module/analytics/automaticEvents.js"() {
|
|
10749
|
+
"use strict";
|
|
10750
|
+
listeners = /* @__PURE__ */ new Set();
|
|
10751
|
+
pending3 = [];
|
|
10752
|
+
MAX_PENDING2 = 50;
|
|
10753
|
+
}
|
|
10754
|
+
});
|
|
10755
|
+
|
|
10752
10756
|
// lib/module/analytics/EventTracker.js
|
|
10753
10757
|
function uuid() {
|
|
10754
10758
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
@@ -11114,6 +11118,38 @@ var init_EventTracker = __esm({
|
|
|
11114
11118
|
}
|
|
11115
11119
|
});
|
|
11116
11120
|
|
|
11121
|
+
// lib/module/core/id/installationId.js
|
|
11122
|
+
function resolveInstallationId() {
|
|
11123
|
+
try {
|
|
11124
|
+
const storage3 = createStorageBackend();
|
|
11125
|
+
const existing = storage3.get(K_INSTALLATION_ID);
|
|
11126
|
+
if (existing) return existing;
|
|
11127
|
+
const id = `inst-${randomSegment()}${randomSegment()}`;
|
|
11128
|
+
storage3.set(K_INSTALLATION_ID, id);
|
|
11129
|
+
return id;
|
|
11130
|
+
} catch {
|
|
11131
|
+
return `inst-ephemeral-${randomSegment()}`;
|
|
11132
|
+
}
|
|
11133
|
+
}
|
|
11134
|
+
function randomSegment() {
|
|
11135
|
+
try {
|
|
11136
|
+
const g = globalThis;
|
|
11137
|
+
if (g.crypto?.getRandomValues) {
|
|
11138
|
+
const bytes = g.crypto.getRandomValues(new Uint8Array(6));
|
|
11139
|
+
return Array.from(bytes).map((b) => b.toString(36).padStart(2, "0")).join("").slice(0, 8);
|
|
11140
|
+
}
|
|
11141
|
+
} catch {
|
|
11142
|
+
}
|
|
11143
|
+
return Math.random().toString(36).slice(2, 10).padEnd(8, "0");
|
|
11144
|
+
}
|
|
11145
|
+
var init_installationId = __esm({
|
|
11146
|
+
"lib/module/core/id/installationId.js"() {
|
|
11147
|
+
"use strict";
|
|
11148
|
+
init_StorageBackend();
|
|
11149
|
+
init_EventTracker();
|
|
11150
|
+
}
|
|
11151
|
+
});
|
|
11152
|
+
|
|
11117
11153
|
// lib/module/features/engage/engageSignals.js
|
|
11118
11154
|
var EngageSignalBus, engageSignals;
|
|
11119
11155
|
var init_engageSignals = __esm({
|
|
@@ -11573,18 +11609,6 @@ function subscribeNativeProgress(onTick) {
|
|
|
11573
11609
|
};
|
|
11574
11610
|
}
|
|
11575
11611
|
}
|
|
11576
|
-
function resolveInstallationId() {
|
|
11577
|
-
try {
|
|
11578
|
-
const storage3 = createStorageBackend();
|
|
11579
|
-
const existing = storage3.get(K_INSTALLATION_ID);
|
|
11580
|
-
if (existing) return existing;
|
|
11581
|
-
const id = `inst-${Math.random().toString(36).slice(2, 10)}${Math.random().toString(36).slice(2, 10)}`;
|
|
11582
|
-
storage3.set(K_INSTALLATION_ID, id);
|
|
11583
|
-
return id;
|
|
11584
|
-
} catch {
|
|
11585
|
-
return `inst-ephemeral-${Math.random().toString(36).slice(2, 10)}`;
|
|
11586
|
-
}
|
|
11587
|
-
}
|
|
11588
11612
|
async function deliverOtaEvents(params) {
|
|
11589
11613
|
const events = otaEventEmitter.flush();
|
|
11590
11614
|
if (!events.length) return;
|
|
@@ -11634,7 +11658,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
11634
11658
|
init_OtaEventEmitter();
|
|
11635
11659
|
init_geoCountry();
|
|
11636
11660
|
init_deviceAttributes();
|
|
11637
|
-
|
|
11661
|
+
init_installationId();
|
|
11638
11662
|
init_signature();
|
|
11639
11663
|
init_environment();
|
|
11640
11664
|
init_retry();
|
|
@@ -15374,6 +15398,7 @@ function decodeManual(base64) {
|
|
|
15374
15398
|
}
|
|
15375
15399
|
|
|
15376
15400
|
// lib/module/features/session/BackendSessionAdapter.js
|
|
15401
|
+
init_installationId();
|
|
15377
15402
|
init_version();
|
|
15378
15403
|
init_bridgeAdapter();
|
|
15379
15404
|
init_nativeModule();
|
|
@@ -15509,6 +15534,13 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
|
|
|
15509
15534
|
const payload = {
|
|
15510
15535
|
sessionId: session.sessionId,
|
|
15511
15536
|
deviceId: session.deviceId,
|
|
15537
|
+
// The canonical per-install id — the SAME value OTA events are
|
|
15538
|
+
// reported under, and a DIFFERENT value from deviceId. The
|
|
15539
|
+
// backend stores it on the Device so release health can join a
|
|
15540
|
+
// bundle's installs to that device's sessions. Without it, crash
|
|
15541
|
+
// impact for a release resolves to "no data" for every device
|
|
15542
|
+
// that never registered for push.
|
|
15543
|
+
installationId: resolveInstallationId(),
|
|
15512
15544
|
startedAt: session.startedAt,
|
|
15513
15545
|
// Link the recording row to the always-on analytics row so the
|
|
15514
15546
|
// dashboard can join them. track() events land on the analytics
|