@scalebun/react-native 1.6.1 → 1.6.3

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 CHANGED
@@ -18,13 +18,20 @@ yarn add @scalebun/react-native
18
18
  cd ios && pod install
19
19
  ```
20
20
 
21
- To wire the AppDelegate for Direct APNs push (optional):
21
+ Wire the AppDelegate for **OTA bundle loading** (required for OTA on iOS) and
22
+ Direct APNs push:
22
23
 
23
24
  ```sh
24
25
  npx scalebun init ios # patch the AppDelegate (ObjC auto-patched; Swift prints steps)
25
- npx scalebun init ios --check # verify hooks exist (exit 1 if missing)
26
+ npx scalebun init ios --check # verify hooks + OTA bundleURL exist (exit 1 if missing)
26
27
  ```
27
28
 
29
+ Like Android, iOS OTA needs the app's bundle resolution wired to the OTA slot
30
+ (`bundleURL` → `ScaleBunOtaModule.getBundleURL()`), or updates install but never
31
+ load. `init ios` auto-applies this to a standard ObjC AppDelegate and prints the
32
+ exact snippet for Swift / custom `bundleURL` bodies. **Rebuild the native app
33
+ afterwards.**
34
+
28
35
  ### Android
29
36
 
30
37
  OTA updates need one wiring step in `MainApplication` so a downloaded bundle
@@ -35,6 +35,18 @@ const FAIL_CALL = 'ScaleBunEngage didFailToRegisterForRemoteNotificationsWithErr
35
35
  const REGISTER_SELECTOR = 'didRegisterForRemoteNotificationsWithDeviceToken';
36
36
  const FAIL_SELECTOR = 'didFailToRegisterForRemoteNotificationsWithError';
37
37
 
38
+ // ── OTA bundle resolution (release bundleURL) ────────────────────────────────
39
+ // The release branch of AppDelegate.bundleURL must return the OTA bundle before
40
+ // the one baked into the .ipa, else an OTA update installs and silently never
41
+ // loads. The exact wiring is documented on ScaleBunOtaModule.getBundleURL().
42
+ // Unlike the push hooks (appended methods), this edits INSIDE an existing method,
43
+ // so it is auto-applied only for the standard RN template line and instructed
44
+ // otherwise (and always for Swift).
45
+ const OTA_BUNDLE_CALL = 'getBundleURL';
46
+ // The canonical release-bundle line RN templates emit (whitespace-tolerant).
47
+ const OTA_RELEASE_LINE_RE =
48
+ /return\s*\[\[NSBundle mainBundle\]\s*URLForResource:@"main"\s*withExtension:@"jsbundle"\]\s*;/;
49
+
38
50
  // ── AppDelegate discovery ───────────────────────────────────────────────────
39
51
 
40
52
  /**
@@ -87,6 +99,9 @@ function analyzeObjc(source) {
87
99
  // does not forward to ScaleBun — we must not add a second one.
88
100
  foreignRegisterMethod: definesRegister && !s.includes(REGISTER_CALL),
89
101
  foreignFailMethod: definesFail && !s.includes(FAIL_CALL),
102
+ // OTA release-bundle resolution wired (either the ObjC `[... getBundleURL]`
103
+ // or a Swift `.getBundleURL()` form).
104
+ hasOtaBundleUrl: s.includes(OTA_BUNDLE_CALL),
90
105
  };
91
106
  }
92
107
 
@@ -228,9 +243,60 @@ function patchObjc(source) {
228
243
  if (needFail) summary.push('Added didFailToRegisterForRemoteNotificationsWithError: forwarding');
229
244
  }
230
245
 
246
+ // 5. OTA release-bundle resolution. Auto-apply only the standard template
247
+ // line; otherwise instruct (never rewrite a custom bundleURL body).
248
+ if (!a.hasOtaBundleUrl) {
249
+ const ota = patchBundleUrlForOta(out);
250
+ if (ota.applied) {
251
+ out = ota.source;
252
+ summary.push('Wired release bundleURL → [ScaleBunOtaModule getBundleURL] (OTA)');
253
+ } else {
254
+ manual.push(otaObjcInstructions());
255
+ }
256
+ }
257
+
231
258
  return { changed: out !== String(source), source: out, summary, manual };
232
259
  }
233
260
 
261
+ /**
262
+ * Wire the release branch of AppDelegate.bundleURL to prefer the OTA bundle.
263
+ * Only replaces the canonical RN template line (safe); returns applied:false with
264
+ * a reason when the AppDelegate has a custom bundleURL body we must not rewrite.
265
+ */
266
+ function patchBundleUrlForOta(source) {
267
+ if (source.includes(OTA_BUNDLE_CALL)) return { source, applied: false, reason: 'present' };
268
+ const m = OTA_RELEASE_LINE_RE.exec(source);
269
+ if (!m) return { source, applied: false, reason: 'no-template-line' };
270
+ const lineStart = source.lastIndexOf('\n', m.index) + 1;
271
+ const indent = (source.slice(lineStart, m.index).match(/^\s*/) || [''])[0];
272
+ const replacement =
273
+ `NSURL *scalebunOtaURL = [ScaleBunOtaModule getBundleURL];\n` +
274
+ `${indent}if (scalebunOtaURL) return scalebunOtaURL;\n` +
275
+ `${indent}${m[0]}`;
276
+ return {
277
+ source: source.slice(0, m.index) + replacement + source.slice(m.index + m[0].length),
278
+ applied: true,
279
+ };
280
+ }
281
+
282
+ /** Exact ObjC OTA wiring when the standard template line is not found. */
283
+ function otaObjcInstructions() {
284
+ return [
285
+ 'OTA bundle resolution is not wired. In your AppDelegate bundleURL (release',
286
+ 'branch), return the OTA bundle before the one baked into the .ipa:',
287
+ '',
288
+ ' - (NSURL *)bundleURL {',
289
+ ' #if DEBUG',
290
+ ' return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];',
291
+ ' #else',
292
+ ' NSURL *otaURL = [ScaleBunOtaModule getBundleURL];',
293
+ ' if (otaURL) return otaURL;',
294
+ ' return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];',
295
+ ' #endif',
296
+ ' }',
297
+ ].join('\n');
298
+ }
299
+
234
300
  /** Manual instructions for Swift AppDelegate (auto-patch not supported yet). */
235
301
  function swiftInstructions() {
236
302
  return [
@@ -251,6 +317,15 @@ function swiftInstructions() {
251
317
  ' didFailToRegisterForRemoteNotificationsWithError error: Error) {',
252
318
  ' ScaleBunEngage.didFailToRegisterForRemoteNotifications(error: error)',
253
319
  ' }',
320
+ '',
321
+ ' // OTA bundle resolution — without this, OTA updates install but never load:',
322
+ ' override func bundleURL() -> URL? {',
323
+ ' #if DEBUG',
324
+ ' RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")',
325
+ ' #else',
326
+ ' ScaleBunOtaModule.getBundleURL()',
327
+ ' #endif',
328
+ ' }',
254
329
  ].join('\n');
255
330
  }
256
331
 
@@ -258,9 +333,11 @@ module.exports = {
258
333
  SWIFT_HEADER,
259
334
  IMPORT_LINE,
260
335
  MANAGED_TAG,
336
+ OTA_BUNDLE_CALL,
261
337
  findAppDelegate,
262
338
  analyzeObjc,
263
339
  isFullyWiredObjc,
264
340
  patchObjc,
341
+ patchBundleUrlForOta,
265
342
  swiftInstructions,
266
343
  };
package/bin/scalebun.js CHANGED
@@ -529,22 +529,26 @@ function runInitIos(argv) {
529
529
 
530
530
  // --check: verify, never write.
531
531
  if (isCheck) {
532
- const wired = codemod.isFullyWiredObjc(original);
533
532
  const a = codemod.analyzeObjc(original);
533
+ const pushWired = codemod.isFullyWiredObjc(original);
534
+ const wired = pushWired && a.hasOtaBundleUrl;
534
535
  console.log('');
535
- console.log(paint('Hook status', C.bold));
536
+ console.log(paint('Push hooks (APNs)', C.bold));
536
537
  line(a.hasImport ? OK() : FAIL(), 'ScaleBun Swift import', a.hasImport ? 'present' : null);
537
538
  line(a.hasFinishLaunchingCall ? OK() : FAIL(), 'didFinishLaunching forward', a.hasFinishLaunchingCall ? 'present' : null);
538
539
  line(a.hasRegisterForward ? OK() : FAIL(), 'didRegister...DeviceToken forward', a.hasRegisterForward ? 'present' : null);
539
540
  line(a.hasFailForward ? OK() : FAIL(), 'didFailToRegister forward', a.hasFailForward ? 'present' : null);
541
+ console.log('');
542
+ console.log(paint('OTA bundle resolution', C.bold));
543
+ line(a.hasOtaBundleUrl ? OK() : FAIL(), 'release bundleURL wiring', a.hasOtaBundleUrl ? 'present' : null);
540
544
  const caps = detectIosCapabilities(iosDir);
541
545
  printIosChecklist(caps);
542
546
  console.log('');
543
547
  if (wired) {
544
- console.log(paint('✓ AppDelegate is fully wired for ScaleBun Direct APNs.', C.green));
548
+ console.log(paint('✓ AppDelegate is fully wired for ScaleBun (Direct APNs + OTA).', C.green));
545
549
  process.exit(0);
546
550
  }
547
- console.log(paint('✗ AppDelegate is missing ScaleBun hooks. Run `npx scalebun init ios`.', C.red));
551
+ console.log(paint('✗ AppDelegate is missing ScaleBun wiring. Run `npx scalebun init ios`.', C.red));
548
552
  process.exit(1);
549
553
  }
550
554
 
@@ -909,7 +913,7 @@ function printFullHelp() {
909
913
  console.log('');
910
914
  console.log(paint('SDK setup (bundled with @scalebun/react-native):', C.bold));
911
915
  console.log(' npx scalebun doctor Diagnose push-notification setup');
912
- console.log(' npx scalebun init ios Wire the iOS AppDelegate for Direct APNs');
916
+ console.log(' npx scalebun init ios Wire the iOS AppDelegate for Direct APNs + OTA');
913
917
  console.log(' npx scalebun init android Wire Android MainApplication for OTA bundle loading');
914
918
  console.log(paint(' add --check to verify (exit 1 if missing) or --dry-run to preview', C.dim));
915
919
  console.log('');
@@ -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.6.1";
492
+ SDK_VERSION = "1.6.3";
493
493
  }
494
494
  });
495
495
 
@@ -492,7 +492,7 @@ var SDK_VERSION;
492
492
  var init_version = __esm({
493
493
  "lib/module/core/constants/version.js"() {
494
494
  "use strict";
495
- SDK_VERSION = "1.6.1";
495
+ SDK_VERSION = "1.6.3";
496
496
  }
497
497
  });
498
498
 
@@ -9,5 +9,5 @@ exports.SDK_VERSION = void 0;
9
9
  * can attribute telemetry to the SDK build that produced it.
10
10
  * Keep in sync with package.json "version".
11
11
  */
12
- const SDK_VERSION = exports.SDK_VERSION = '1.6.1';
12
+ const SDK_VERSION = exports.SDK_VERSION = '1.6.3';
13
13
  //# sourceMappingURL=version.js.map
@@ -3,5 +3,5 @@
3
3
  * can attribute telemetry to the SDK build that produced it.
4
4
  * Keep in sync with package.json "version".
5
5
  */
6
- export const SDK_VERSION = '1.6.1';
6
+ export const SDK_VERSION = '1.6.3';
7
7
  //# sourceMappingURL=version.js.map
@@ -3,5 +3,5 @@
3
3
  * can attribute telemetry to the SDK build that produced it.
4
4
  * Keep in sync with package.json "version".
5
5
  */
6
- export declare const SDK_VERSION = "1.6.1";
6
+ export declare const SDK_VERSION = "1.6.3";
7
7
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalebun/react-native",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "Production-grade React Native SDK for ScaleBun",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -105,7 +105,7 @@
105
105
  "@babel/runtime": "^7.25.0",
106
106
  "@jridgewell/sourcemap-codec": "1.5.5",
107
107
  "@jridgewell/trace-mapping": "0.3.31",
108
- "@scalebun/cli": "^1.6.1"
108
+ "@scalebun/cli": "^1.6.3"
109
109
  },
110
110
  "codegenConfig": {
111
111
  "name": "ScaleBunSpec",
@@ -3,4 +3,4 @@
3
3
  * can attribute telemetry to the SDK build that produced it.
4
4
  * Keep in sync with package.json "version".
5
5
  */
6
- export const SDK_VERSION = '1.6.1';
6
+ export const SDK_VERSION = '1.6.3';