@livx.cc/native-kit 0.32.0 → 0.32.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livx.cc/native-kit",
3
- "version": "0.32.0",
3
+ "version": "0.32.3",
4
4
  "description": "Isomorphic native-capabilities kit for PWAs \u2014 same API in browser and in an appwrap native shell. Zero dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Elya Livshitz",
@@ -45,6 +45,12 @@ export class AppwrapAdapter implements NativeKitAdapter {
45
45
  return this.request<T>(method, params, opts?.timeoutMs ?? 10_000);
46
46
  }
47
47
 
48
+ /** `'none'` or `0` → no watchdog (dismiss-bound calls); otherwise the deadline in ms. */
49
+ private static resolveTimeout(timeoutMs: number | 'none'): number | null {
50
+ if (timeoutMs === 'none' || timeoutMs === 0) return null;
51
+ return timeoutMs;
52
+ }
53
+
48
54
  on(event: string, cb: (payload: unknown) => void): Unsubscribe {
49
55
  let set = this.listeners.get(event);
50
56
  if (!set) this.listeners.set(event, (set = new Set()));
@@ -52,14 +58,17 @@ export class AppwrapAdapter implements NativeKitAdapter {
52
58
  return () => set!.delete(cb);
53
59
  }
54
60
 
55
- private request<T>(method: string, params: unknown, timeoutMs: number): Promise<T> {
61
+ private request<T>(method: string, params: unknown, timeoutMs: number | 'none'): Promise<T> {
56
62
  const id = `k${++this.seq}`;
57
63
  const envelope: RequestEnvelope = { v: 1, id, kind: 'request', method, params };
64
+ const deadline = AppwrapAdapter.resolveTimeout(timeoutMs);
58
65
  return new Promise<T>((resolve, reject) => {
59
- const timer = setTimeout(() => {
66
+ // `null` deadline = dismiss-bound call: no watchdog, resolves only on the
67
+ // native response (e.g. the user closes the sheet).
68
+ const timer = deadline === null ? undefined : setTimeout(() => {
60
69
  this.pending.delete(id);
61
- reject(new KitError('TIMEOUT', `${method} timed out after ${timeoutMs}ms`));
62
- }, timeoutMs);
70
+ reject(new KitError('TIMEOUT', `${method} timed out after ${deadline}ms`));
71
+ }, deadline);
63
72
  this.pending.set(id, {
64
73
  resolve: (v) => { clearTimeout(timer); resolve(v as T); },
65
74
  reject: (e) => { clearTimeout(timer); reject(e); },
package/src/core/types.ts CHANGED
@@ -71,9 +71,19 @@ export class KitError extends Error {
71
71
  export type Unsubscribe = () => void;
72
72
 
73
73
  export interface InvokeOptions {
74
- /** Per-call response deadline. Interactive flows (pickers, dialogs, auth
75
- * prompts) should pass a generous one — the user may take their time. */
76
- timeoutMs?: number;
74
+ /** Per-call response deadline (default 10_000ms).
75
+ *
76
+ * Interactive flows (pickers, dialogs, auth prompts) should pass a generous
77
+ * value — the user may take their time.
78
+ *
79
+ * Pass `'none'` (or `0`) to DISABLE the watchdog entirely. Use this ONLY for
80
+ * dismiss-bound / present-and-wait calls: native UI that resolves solely when
81
+ * the user dismisses a sheet/modal (manage-subscriptions sheet,
82
+ * ASWebAuthenticationSession, share sheet, …). For these there is no
83
+ * meaningful deadline — a watchdog could only false-timeout mid-interaction
84
+ * and force a spurious fallback. Do NOT use it for fire-and-fast calls: a
85
+ * finite timeout is what surfaces a real native hang. */
86
+ timeoutMs?: number | 'none';
77
87
  }
78
88
 
79
89
  export interface NativeKitAdapter {
@@ -132,7 +132,9 @@ export class BillingModule {
132
132
  if (this.capability !== 'native') {
133
133
  throw new KitError('UNSUPPORTED', 'The in-app subscriptions sheet is iOS-only (StoreKit 2). Use manageSubscriptions() on web/Android.');
134
134
  }
135
- return this.kit.invoke('billing.manageSubscriptionsSheet');
135
+ // Dismiss-bound: resolves only when the USER closes the sheet (no meaningful deadline).
136
+ // A watchdog could only false-timeout mid-sheet → spurious fallback, so disable it.
137
+ return this.kit.invoke('billing.manageSubscriptionsSheet', undefined, { timeoutMs: 'none' });
136
138
  }
137
139
 
138
140
  /** Out-of-band transactions (renewals, Ask-to-Buy, cross-device). Native streams only. */
@@ -39,9 +39,9 @@ export class OAuthModule {
39
39
  }
40
40
 
41
41
  authorize(params: OAuthAuthorizeParams): Promise<OAuthResult> {
42
- // Long timeout: a real sign-in (password + 2FA + consent in the system browser) easily exceeds
43
- // the 10s invoke default — without this the kit abandons the request id before the redirect
44
- // returns and the login is silently dropped. Matches the other interactive flows (pickers 120s).
45
- return this.kit.invoke('oauth.authorize', params as unknown as Record<string, unknown>, { timeoutMs: 300_000 });
42
+ // Dismiss-bound: the ASWebAuthenticationSession / Custom Tab resolves only when the user
43
+ // finishes (password + 2FA + consent) or cancels — there is no meaningful deadline. A watchdog
44
+ // would abandon the request id before the redirect returns and silently drop the login.
45
+ return this.kit.invoke('oauth.authorize', params as unknown as Record<string, unknown>, { timeoutMs: 'none' });
46
46
  }
47
47
  }
@@ -9,6 +9,11 @@ export type PushPlatform = 'apns' | 'fcm';
9
9
  export interface PushToken {
10
10
  platform: PushPlatform;
11
11
  token: string;
12
+ /** The app's bundle/package id. On iOS this is the APNs `apns-topic` your backend MUST send
13
+ * (APNs rejects a mismatch with `DeviceTokenNotForTopic`); on Android it's the package id —
14
+ * informational, since FCM doesn't use apns-topic. Optional: absent on web / un-provisioned
15
+ * builds and older shells that don't supply it (a missing topic is tolerated). */
16
+ topic?: string;
12
17
  }
13
18
 
14
19
  /** A remote message surfaced to the page. `data` is the sender's custom payload; `title`/`body`
@@ -27,8 +32,9 @@ export interface PushMessage {
27
32
  * ```ts
28
33
  * if (kit.push.capability === 'native') {
29
34
  * if (await kit.push.requestPermission() === 'granted') {
30
- * const { platform, token } = await kit.push.register();
31
- * await fetch('/api/push/register', { method:'POST', body: JSON.stringify({ platform, token }) });
35
+ * const { platform, token, topic } = await kit.push.register();
36
+ * // `topic` = the iOS bundle id → your backend sets it as the APNs apns-topic header.
37
+ * await fetch('/api/push/register', { method:'POST', body: JSON.stringify({ platform, token, topic }) });
32
38
  * }
33
39
  * kit.push.onMessage((m) => …); // foreground delivery
34
40
  * kit.push.onTap((m) => …); // user opened a notification