@livx.cc/appwrap 0.35.0 → 0.36.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livx.cc/appwrap",
3
- "version": "0.35.0",
3
+ "version": "0.36.1",
4
4
  "description": "Wrap any PWA into a native app with native capabilities (appwrap runtime + @livx.cc/native-kit).",
5
5
  "license": "MIT",
6
6
  "author": "Elya Livshitz",
@@ -270,6 +270,21 @@ export const MODULES: ModuleManifest[] = [
270
270
  ios: { permissions: [{ key: 'NSUserTrackingUsageDescription', domain: 'tracking', defaultUsage: 'Allow tracking to deliver a more personalized experience and measure ad performance.' }] },
271
271
  },
272
272
 
273
+ // ── appleSignIn — native Sign in with Apple (iOS ASAuthorization) — opt-in, STRIPPABLE ──
274
+ // The native account-sheet alternative to the web-OAuth path: ASAuthorizationAppleIDProvider uses the
275
+ // App ID (bundle) and returns the identityToken + nonce DIRECTLY — no Services ID, no https Return URL,
276
+ // no browser redirect (which Apple rejects for custom-scheme redirect_uris). The PWA feeds the result
277
+ // to Firebase signInWithCredential('apple.com', { idToken, rawNonce }). iOS-only (`ios:true`/
278
+ // `android:false`) — Sign in with Apple has NO native Android SDK (the kit reports 'none' there and the
279
+ // app falls back to its web Apple auth). Stamps the `com.apple.developer.applesignin` entitlement ONLY
280
+ // when active (gated, like push's aps-environment) — a non-Apple-SignIn build signs without it. No
281
+ // permission string. Strippable own handler `handlers-apple-signin.ts` (in OPTIONAL_GROUPS).
282
+ {
283
+ name: 'appleSignIn', group: 'appleSignIn',
284
+ capabilities: { appleSignIn: { ios: true, android: false } },
285
+ ios: { entitlements: { 'com.apple.developer.applesignin': ['Default'] } },
286
+ },
287
+
273
288
  // ── backgroundTask — headless background execution (HEADLESS JS HANDLER) — opt-in, STRIPPABLE ──
274
289
  // The OS wakes the app (possibly cold, no visible WebView) for a permitted task id; the shell builds
275
290
  // an OFFSCREEN WebView, loads the app conveying the id (the handshake reports it), awaits the JS
@@ -289,7 +304,7 @@ export const MODULES: ModuleManifest[] = [
289
304
 
290
305
  /** Opt-in registration groups that own their own NS handler file (strippable when inactive). Core
291
306
  * groups (core/extended/parity/system/media/billing) are always bundled; only these are CLI-gated. */
292
- export const OPTIONAL_GROUPS = ['health', 'oauth', 'reviews', 'scanner', 'speech', 'tracking', 'backgroundTask'] as const;
307
+ export const OPTIONAL_GROUPS = ['health', 'oauth', 'reviews', 'scanner', 'speech', 'tracking', 'appleSignIn', 'backgroundTask'] as const;
293
308
 
294
309
  /** Resolve the active capability map for the handshake from a set of active capability names. */
295
310
  export function buildCapabilityMap(
@@ -17,30 +17,54 @@ export function startDevMenu(): void {
17
17
  else if (isAndroid) startAndroidShake();
18
18
  }
19
19
 
20
- // ── shake detection (total acceleration magnitude in g incl. gravity, debounced) ──
21
- const SHAKE_G = 1.8; // spike vs. ~1g rest
20
+ // ── shake detection — deliberate, robust against incidental motion (walking, bumps) ──
21
+ // A real "shake to open" is a sustained back-and-forth: several HARD spikes, close together, that
22
+ // REVERSE direction each time. Walking/jostle produces occasional same-direction spikes with gaps —
23
+ // so we require (a) a high threshold, (b) direction reversal between consecutive spikes, and (c) a
24
+ // short inter-spike gap that RESETS the count if you pause. The bar is a firm wrist-shake, not a step.
25
+ const SHAKE_G = 2.3; // hard spike vs. ~1g rest (was 1.8 — too low, walking cleared it)
26
+ const SHAKE_SPIKES = 4; // direction-reversing spikes needed (≈2 full back-and-forth shakes)
27
+ const SHAKE_GAP_MS = 400; // max gap between spikes; a longer pause resets the streak
28
+ const MENU_DEBOUNCE_MS = 1500; // don't re-open right after a menu
22
29
  let shakeMm: CMMotionManager | null = null; // hold a ref so CoreMotion isn't GC'd
23
- let firstSpikeAt = 0;
24
30
  let spikes = 0;
31
+ let lastSpikeAt = 0;
32
+ let lastSpikeDir = 0; // sign of the dominant axis at the last spike; reversal = real shake
25
33
  let lastMenuAt = 0;
26
34
 
27
- function onAccelMagnitude(g: number): void {
35
+ /** @param g total accel magnitude in g (rest ≈ 1)
36
+ * @param dir sign (+1/-1) of the dominant-axis acceleration — used to detect back-and-forth reversal */
37
+ function onAccelMagnitude(g: number, dir: number): void {
28
38
  if (g < SHAKE_G) return;
29
39
  const now = Date.now();
30
- if (now - lastMenuAt < 1500) return; // don't re-open right after a menu
31
- if (now - firstSpikeAt > 800) {
32
- firstSpikeAt = now; // start a fresh window
40
+ if (now - lastMenuAt < MENU_DEBOUNCE_MS) return;
41
+ // A spike that's too soon after the last counts as the SAME thrust, not a new one — ignore it so a
42
+ // single hard jolt's ringing doesn't rack up the count.
43
+ if (now - lastSpikeAt < 80) return;
44
+ // Reset the streak unless this spike continues a fast, direction-reversing sequence.
45
+ if (now - lastSpikeAt > SHAKE_GAP_MS || dir === lastSpikeDir) {
33
46
  spikes = 1;
34
- return;
47
+ } else {
48
+ spikes++;
35
49
  }
36
- if (++spikes >= 2) {
50
+ lastSpikeAt = now;
51
+ lastSpikeDir = dir;
52
+ if (spikes >= SHAKE_SPIKES) {
37
53
  spikes = 0;
38
- firstSpikeAt = 0;
54
+ lastSpikeAt = 0;
55
+ lastSpikeDir = 0;
39
56
  lastMenuAt = now;
40
57
  void showDevMenu();
41
58
  }
42
59
  }
43
60
 
61
+ /** Sign of the largest-magnitude component — the axis the shake is thrusting along. */
62
+ function dominantDir(x: number, y: number, z: number): number {
63
+ const ax = Math.abs(x), ay = Math.abs(y), az = Math.abs(z);
64
+ const v = ax >= ay && ax >= az ? x : ay >= az ? y : z;
65
+ return v >= 0 ? 1 : -1;
66
+ }
67
+
44
68
  function startIOSShake(): void {
45
69
  // Poll `mm.deviceMotion` on a JS timer — CoreMotion's queue-handler block is fragile under
46
70
  // NativeScript (can silently stop firing on-device); mirrors the motion.start handler.
@@ -52,10 +76,12 @@ function startIOSShake(): void {
52
76
  setInterval(() => {
53
77
  const m = mm.deviceMotion;
54
78
  if (!m) return; // first sample not ready
55
- const x = m.userAcceleration.x + m.gravity.x; // total accel in g (rest ≈ 1)
56
- const y = m.userAcceleration.y + m.gravity.y;
57
- const z = m.userAcceleration.z + m.gravity.z;
58
- onAccelMagnitude(Math.sqrt(x * x + y * y + z * z));
79
+ const ux = m.userAcceleration.x, uy = m.userAcceleration.y, uz = m.userAcceleration.z;
80
+ const x = ux + m.gravity.x; // total accel in g (rest ≈ 1) — magnitude includes gravity
81
+ const y = uy + m.gravity.y;
82
+ const z = uz + m.gravity.z;
83
+ // Direction from gravity-free userAcceleration (gravity is constant → would mask the back-and-forth).
84
+ onAccelMagnitude(Math.sqrt(x * x + y * y + z * z), dominantDir(ux, uy, uz));
59
85
  }, 100);
60
86
  }
61
87
 
@@ -66,11 +92,19 @@ function startAndroidShake(): void {
66
92
  const accel = sm.getDefaultSensor(android.hardware.Sensor.TYPE_ACCELEROMETER);
67
93
  if (!accel) return;
68
94
  const G = 9.80665;
95
+ // Low-pass gravity estimate so we can derive gravity-free direction (mirrors iOS userAcceleration);
96
+ // the raw accelerometer includes gravity, which is constant and would mask the back-and-forth.
97
+ const grav = [0, 0, 0];
98
+ const alpha = 0.8;
69
99
  const listener = new android.hardware.SensorEventListener({
70
100
  onAccuracyChanged() {},
71
101
  onSensorChanged(e: android.hardware.SensorEvent) {
72
102
  const v = e.values;
73
- onAccelMagnitude(Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) / G);
103
+ grav[0] = alpha * grav[0] + (1 - alpha) * v[0];
104
+ grav[1] = alpha * grav[1] + (1 - alpha) * v[1];
105
+ grav[2] = alpha * grav[2] + (1 - alpha) * v[2];
106
+ const lx = v[0] - grav[0], ly = v[1] - grav[1], lz = v[2] - grav[2]; // linear (gravity-free)
107
+ onAccelMagnitude(Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) / G, dominantDir(lx, ly, lz));
74
108
  },
75
109
  });
76
110
  sm.registerListener(listener, accel, android.hardware.SensorManager.SENSOR_DELAY_UI);
@@ -0,0 +1,169 @@
1
+ import { Utils, isIOS } from '@nativescript/core';
2
+ import { bridge } from './bridge';
3
+ import { sha256Hex } from './sha256';
4
+
5
+ const err = (code: string, message: string) => Object.assign(new Error(message), { code });
6
+
7
+ // Keep strong refs while the system sheet is up — ARC frees the controller + its delegate + context
8
+ // provider the moment the JS locals fall out of scope, which silently cancels the flow mid-handshake
9
+ // (same lesson as handlers-oauth.ts `activeSession`/`activeProvider`).
10
+ let activeController: ASAuthorizationController | null = null;
11
+ let activeDelegate: AppleSignInDelegate | null = null;
12
+ let activeProvider: AppleSignInContextProvider | null = null;
13
+
14
+ @NativeClass()
15
+ class AppleSignInContextProvider extends NSObject implements ASAuthorizationControllerPresentationContextProviding {
16
+ static ObjCProtocols = [ASAuthorizationControllerPresentationContextProviding];
17
+ static new(): AppleSignInContextProvider {
18
+ return <AppleSignInContextProvider>super.new();
19
+ }
20
+ presentationAnchorForAuthorizationController(_controller: ASAuthorizationController): UIWindow {
21
+ return (
22
+ Utils.ios.getRootViewController()?.view?.window ??
23
+ UIApplication.sharedApplication.keyWindow
24
+ );
25
+ }
26
+ }
27
+
28
+ /** Decode an Apple-returned NSData JWT/code blob into a UTF-8 string (identityToken/authorizationCode). */
29
+ function dataToUtf8(data: NSData | null): string | undefined {
30
+ if (!data) return undefined;
31
+ const s = NSString.alloc().initWithDataEncoding(data, NSUTF8StringEncoding);
32
+ return s ? String(s) : undefined;
33
+ }
34
+
35
+ @NativeClass()
36
+ class AppleSignInDelegate extends NSObject implements ASAuthorizationControllerDelegate {
37
+ static ObjCProtocols = [ASAuthorizationControllerDelegate];
38
+ // Per-call settlement (set right after `.new()`). The shared `settle` clears the ARC refs + recovers
39
+ // the WebView before resolving/rejecting, so it runs exactly once per flow.
40
+ rawNonce = '';
41
+ settle!: (fn: () => void) => void;
42
+ resolve!: (v: unknown) => void;
43
+ reject!: (e: unknown) => void;
44
+ static new(): AppleSignInDelegate {
45
+ return <AppleSignInDelegate>super.new();
46
+ }
47
+
48
+ authorizationControllerDidCompleteWithAuthorization(
49
+ _controller: ASAuthorizationController,
50
+ authorization: ASAuthorization
51
+ ): void {
52
+ const cred = authorization.credential as ASAuthorizationAppleIDCredential;
53
+ const identityToken = dataToUtf8(cred.identityToken ?? null);
54
+ if (!identityToken) {
55
+ this.settle(() => this.reject(err('NATIVE_ERROR', 'appleSignIn: Apple returned no identityToken')));
56
+ return;
57
+ }
58
+ const authorizationCode = dataToUtf8(cred.authorizationCode ?? null);
59
+
60
+ // First-authorization-only profile. Apple omits name/email on subsequent sign-ins per Apple ID.
61
+ let name: { givenName?: string; familyName?: string; displayName?: string } | undefined;
62
+ if (cred.fullName) {
63
+ const givenName = cred.fullName.givenName || undefined;
64
+ const familyName = cred.fullName.familyName || undefined;
65
+ // OS-composed display name (locale-aware) — convenience; the consumer can also build its own.
66
+ const composed = NSPersonNameComponentsFormatter.localizedStringFromPersonNameComponentsStyleOptions(
67
+ cred.fullName,
68
+ NSPersonNameComponentsFormatterStyle.Default,
69
+ 0 as NSPersonNameComponentsFormatterOptions
70
+ );
71
+ const displayName = (composed && String(composed)) || undefined;
72
+ if (givenName || familyName || displayName) name = { givenName, familyName, displayName };
73
+ }
74
+ const email = cred.email || undefined;
75
+ const user = name || email ? { ...(name ? { name } : {}), ...(email ? { email } : {}) } : undefined;
76
+
77
+ this.settle(() =>
78
+ this.resolve({ identityToken, authorizationCode, nonce: this.rawNonce, ...(user ? { user } : {}) })
79
+ );
80
+ }
81
+
82
+ authorizationControllerDidCompleteWithError(_controller: ASAuthorizationController, error: NSError): void {
83
+ // ASAuthorizationError.Canceled === 1001 (user dismissed the sheet) → resolve cancelled, never throw.
84
+ if (error.code === ASAuthorizationError.Canceled) {
85
+ this.settle(() => this.resolve({ cancelled: true }));
86
+ return;
87
+ }
88
+ this.settle(() => this.reject(err('NATIVE_ERROR', error.localizedDescription ?? 'appleSignIn failed')));
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Native Sign in with Apple (iOS, `appleSignIn` module). Strippable own-handler file (registered only
94
+ * when the module is active) — a build without it compiles NO ASAuthorization code and stamps no
95
+ * entitlement. Presents `ASAuthorizationController` (the system account sheet) and returns the Apple
96
+ * `identityToken` + raw nonce DIRECTLY to JS, so the PWA can do `signInWithCredential` on Firebase.
97
+ *
98
+ * The caller passes a RAW nonce; we send SHA256(nonce) to Apple (Apple binds the id_token to the hash)
99
+ * and return the raw nonce unchanged so the web app hands `rawNonce` to Firebase.
100
+ *
101
+ * Name/email come back ONLY on the first authorization per Apple ID — we relay them when present.
102
+ * Cancel resolves `{ cancelled: true }` (never throws), matching the scanner/oauth contract.
103
+ *
104
+ * iOS-only: the capability is gated `ios:true`/`android:false`, so the kit short-circuits off iOS
105
+ * before reaching the bridge. The isIOS guard is defence-in-depth.
106
+ *
107
+ * DEVICE-VERIFIED (iPhone 13 Pro Max, 2026-06-24, hello-pwa under paid team RDYDSWE9RB): the native
108
+ * ASAuthorization sheet presented, returned a real Apple-signed identityToken (JWT) + the raw nonce +
109
+ * first-auth user email, and dismissed without freezing (recoverAfterNativeSurface held). The
110
+ * com.apple.developer.applesignin entitlement signs only on a paid team — the free personal team can't
111
+ * hold the Sign-in-with-Apple capability (same class as push aps-environment).
112
+ */
113
+ export function registerAppleSignInHandlers(): void {
114
+ if (!isIOS) return;
115
+
116
+ bridge.register(
117
+ 'appleSignIn.signIn',
118
+ ({ nonce, scopes }: { nonce: string; scopes?: Array<'name' | 'email'> }) =>
119
+ new Promise((resolve, reject) => {
120
+ const rawNonce = String(nonce ?? '');
121
+ if (!rawNonce) { reject(err('NATIVE_ERROR', 'appleSignIn.signIn: empty nonce')); return; }
122
+ const want = new Set(scopes ?? ['name', 'email']);
123
+
124
+ Utils.dispatchToMainThread(() => {
125
+ try {
126
+ const provider = ASAuthorizationAppleIDProvider.alloc().init();
127
+ const request = provider.createRequest();
128
+ // Apple binds the returned id_token to SHA256(nonce); we hand back the RAW nonce to JS.
129
+ request.nonce = sha256Hex(rawNonce);
130
+ const scopeList: string[] = [];
131
+ if (want.has('name')) scopeList.push(ASAuthorizationScopeFullName);
132
+ if (want.has('email')) scopeList.push(ASAuthorizationScopeEmail);
133
+ request.requestedScopes = NSArray.arrayWithArray<string>(scopeList as any);
134
+
135
+ const settle = (fn: () => void) => {
136
+ activeController = null;
137
+ activeDelegate = null;
138
+ activeProvider = null;
139
+ // An ASAuthorization sheet can leave the WebView frozen on dismiss like the other native
140
+ // surfaces — recover from its completion (shared framework fix).
141
+ bridge.getWebView()?.recoverAfterNativeSurface();
142
+ fn();
143
+ };
144
+
145
+ const delegate = AppleSignInDelegate.new();
146
+ delegate.rawNonce = rawNonce;
147
+ delegate.settle = settle;
148
+ delegate.resolve = resolve;
149
+ delegate.reject = reject;
150
+
151
+ const controller = ASAuthorizationController.alloc().initWithAuthorizationRequests(
152
+ NSArray.arrayWithObject<ASAuthorizationRequest>(request)
153
+ );
154
+ activeDelegate = delegate;
155
+ activeProvider = AppleSignInContextProvider.new();
156
+ controller.delegate = delegate;
157
+ controller.presentationContextProvider = activeProvider;
158
+ activeController = controller;
159
+ controller.performRequests();
160
+ } catch (e: any) {
161
+ activeController = null;
162
+ activeDelegate = null;
163
+ activeProvider = null;
164
+ reject(err('NATIVE_ERROR', e?.message ?? 'appleSignIn failed to start'));
165
+ }
166
+ });
167
+ })
168
+ );
169
+ }
@@ -10,7 +10,7 @@ import { ACTIVE_MODULE_NAMES } from './active-modules.generated';
10
10
  import { consumePendingBackgroundTaskId } from './background-context';
11
11
 
12
12
  /** Build identifier for the native shell bundle — bump per deploy to spot stale bundles. */
13
- export const SHELL_BUILD = 'global-observer-1';
13
+ export const SHELL_BUILD = 'apple-signin-1';
14
14
 
15
15
  /** Version status the web side (native-kit `kit.updates`) reports via `app.reportWebVersion`. */
16
16
  export interface WebVersionInfo { current?: string; latest?: string; build?: string | number; updateAvailable?: boolean; }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Minimal, dependency-free SHA-256 → lowercase hex. Used by the `appleSignIn` handler to hash the raw
3
+ * nonce before sending it to Apple (Apple binds the returned id_token to SHA256(nonce); the raw nonce
4
+ * goes back to JS for Firebase). NativeScript ships no WebCrypto, and a pointer-juggling CommonCrypto
5
+ * FFI call is exactly the fragile any-typed interop the project avoids — a small pure-JS hash is
6
+ * deterministic and unit-testable without a device. Input is treated as a UTF-8 string.
7
+ */
8
+
9
+ const K = new Uint32Array([
10
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
11
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
12
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
13
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
14
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
15
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
16
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
17
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
18
+ ]);
19
+
20
+ function utf8Bytes(str: string): number[] {
21
+ const out: number[] = [];
22
+ for (let i = 0; i < str.length; i++) {
23
+ let c = str.charCodeAt(i);
24
+ if (c < 0x80) out.push(c);
25
+ else if (c < 0x800) out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
26
+ else if (c >= 0xd800 && c <= 0xdbff) {
27
+ // Surrogate pair → full code point.
28
+ const lo = str.charCodeAt(++i);
29
+ c = 0x10000 + ((c & 0x3ff) << 10) + (lo & 0x3ff);
30
+ out.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
31
+ } else out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
32
+ }
33
+ return out;
34
+ }
35
+
36
+ const rotr = (x: number, n: number) => (x >>> n) | (x << (32 - n));
37
+
38
+ /** SHA-256 of a UTF-8 string, lowercase hex (64 chars). */
39
+ export function sha256Hex(message: string): string {
40
+ const bytes = utf8Bytes(message);
41
+ const bitLen = bytes.length * 8;
42
+ bytes.push(0x80);
43
+ while (bytes.length % 64 !== 56) bytes.push(0);
44
+ // 64-bit big-endian length (high word is 0 for any realistic nonce).
45
+ for (let i = 7; i >= 0; i--) bytes.push((bitLen / 2 ** (8 * i)) & 0xff);
46
+
47
+ const h = new Uint32Array([
48
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
49
+ ]);
50
+ const w = new Uint32Array(64);
51
+
52
+ for (let i = 0; i < bytes.length; i += 64) {
53
+ for (let t = 0; t < 16; t++) {
54
+ w[t] = (bytes[i + 4 * t] << 24) | (bytes[i + 4 * t + 1] << 16) | (bytes[i + 4 * t + 2] << 8) | bytes[i + 4 * t + 3];
55
+ }
56
+ for (let t = 16; t < 64; t++) {
57
+ const s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ (w[t - 15] >>> 3);
58
+ const s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ (w[t - 2] >>> 10);
59
+ w[t] = (w[t - 16] + s0 + w[t - 7] + s1) | 0;
60
+ }
61
+ let [a, b, c, d, e, f, g, hh] = [h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]];
62
+ for (let t = 0; t < 64; t++) {
63
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
64
+ const ch = (e & f) ^ (~e & g);
65
+ const t1 = (hh + S1 + ch + K[t] + w[t]) | 0;
66
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
67
+ const maj = (a & b) ^ (a & c) ^ (b & c);
68
+ const t2 = (S0 + maj) | 0;
69
+ hh = g; g = f; f = e; e = (d + t1) | 0; d = c; c = b; b = a; a = (t1 + t2) | 0;
70
+ }
71
+ h[0] = (h[0] + a) | 0; h[1] = (h[1] + b) | 0; h[2] = (h[2] + c) | 0; h[3] = (h[3] + d) | 0;
72
+ h[4] = (h[4] + e) | 0; h[5] = (h[5] + f) | 0; h[6] = (h[6] + g) | 0; h[7] = (h[7] + hh) | 0;
73
+ }
74
+
75
+ let hex = '';
76
+ for (let i = 0; i < 8; i++) hex += (h[i] >>> 0).toString(16).padStart(8, '0');
77
+ return hex;
78
+ }
package/src/cli.ts CHANGED
@@ -179,6 +179,7 @@ const OPTIONAL_GROUP_HANDLERS: Record<string, { file: string; fn: string }> = {
179
179
  scanner: { file: './handlers-scanner', fn: 'registerScannerHandlers' },
180
180
  speech: { file: './handlers-speech', fn: 'registerSpeechHandlers' },
181
181
  tracking: { file: './handlers-tracking', fn: 'registerTrackingHandlers' },
182
+ appleSignIn: { file: './handlers-apple-signin', fn: 'registerAppleSignInHandlers' },
182
183
  backgroundTask: { file: './handlers-background', fn: 'registerBackgroundTaskHandlers' },
183
184
  };
184
185