@livx.cc/native-kit 0.45.3 → 0.45.4

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.45.3",
3
+ "version": "0.45.4",
4
4
  "description": "Isomorphic native-capabilities kit for PWAs — same API in browser and in an appwrap native shell. Zero dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Elya Livshitz",
@@ -268,7 +268,36 @@ export class WebAdapter implements NativeKitAdapter {
268
268
  case 'notifications.schedule': {
269
269
  if (Notification.permission !== 'granted') throw new KitError('DENIED', 'Permission not granted');
270
270
  const delay = (p.delaySec ?? 1) * 1000;
271
- setTimeout(() => new Notification(p.title, { body: p.body }), delay);
271
+ // Rich options degrade one step at a time rather than all-or-nothing: `icon`/`image` are
272
+ // honoured by the plain Notification constructor, but ACTION BUTTONS exist only on
273
+ // ServiceWorkerRegistration.showNotification — so use the SW when there is one and fall back
274
+ // to a button-less banner when there isn't, instead of refusing the call.
275
+ const opts: NotificationOptions & { image?: string; actions?: Array<{ action: string; title: string }> } = {
276
+ body: p.body,
277
+ icon: p.icon,
278
+ tag: p.sender || undefined,
279
+ silent: !!p.silent,
280
+ data: { url: p.deepLink, actions: p.actions },
281
+ };
282
+ if (p.image) opts.image = p.image;
283
+ const buttons: Array<{ id: string; title: string }> = Array.isArray(p.actions) ? p.actions.slice(0, 3) : [];
284
+ setTimeout(async () => {
285
+ const reg = buttons.length ? await navigator.serviceWorker?.ready.catch(() => null) : null;
286
+ if (reg && buttons.length) {
287
+ opts.actions = buttons.map((b) => ({ action: b.id, title: b.title }));
288
+ await reg.showNotification(p.title, opts).catch((e: Error) => console.warn('[kit] showNotification failed', e));
289
+ return;
290
+ }
291
+ const n = new Notification(p.title, opts);
292
+ // A tap on the banner focuses this tab and follows the deep link when it is a web URL —
293
+ // a custom scheme is a native-only target and navigating to it here would just error.
294
+ n.onclick = () => {
295
+ window.focus();
296
+ const url = String(p.deepLink ?? '');
297
+ if (/^https?:\/\//i.test(url)) location.href = url;
298
+ n.close();
299
+ };
300
+ }, delay);
272
301
  return { id: p.id ?? Date.now() % 100000 } as T;
273
302
  }
274
303
  case 'notifications.pending':
package/src/index.ts CHANGED
@@ -27,7 +27,7 @@ export type { Orientation, OrientationLock } from './modules/screen';
27
27
  export type { KeyboardInfo } from './modules/keyboard';
28
28
  export type { FsDirectory, FsEncoding, FsEntry, PickedFile } from './modules/fs';
29
29
  export type { DeviceInfo } from './modules/device';
30
- export type { ScheduleOptions } from './modules/notifications';
30
+ export type { ScheduleOptions, NotificationAction } from './modules/notifications';
31
31
  export type { PushMessage, PushPlatform, PushToken } from './modules/push';
32
32
  export type { GeoPosition } from './modules/geo';
33
33
  export type { HeadingSample } from './modules/heading';
@@ -44,6 +44,31 @@ export interface ScheduleOptions {
44
44
  * falls back to the DEFAULT alert — never to silence. iOS only; Android uses its channel sound.
45
45
  */
46
46
  sound?: string;
47
+ /**
48
+ * HERO ARTWORK — an image URL or data-URI that turns the banner into a rich card: an expandable
49
+ * attachment on iOS (UNNotificationAttachment) and a BigPictureStyle image on Android. A failed
50
+ * download degrades to a plain banner rather than dropping the notification.
51
+ */
52
+ image?: string;
53
+ /**
54
+ * Up to 3 tappable BUTTONS. Each carries its own `deepLink` (falling back to the notification's
55
+ * when omitted), delivered through the same `deeplink.open` event as a banner tap — so
56
+ * `kit.lifecycle.onDeepLink` routes a button tap with no extra wiring. iOS draws 2 on a collapsed
57
+ * banner (4 expanded), Android 3; extra entries, and entries missing an id or title, are dropped.
58
+ * On the web they need a service worker (buttons are a ServiceWorkerRegistration feature) —
59
+ * without one the banner still shows, just without buttons.
60
+ */
61
+ actions?: NotificationAction[];
62
+ }
63
+
64
+ /** One tappable button on a rich notification. */
65
+ export interface NotificationAction {
66
+ /** Stable id — what identifies the tapped button. */
67
+ id: string;
68
+ /** Button label. */
69
+ title: string;
70
+ /** Where the tap lands. Defaults to the notification's own `deepLink`. */
71
+ deepLink?: string;
47
72
  }
48
73
 
49
74
  export class NotificationsModule {