@livx.cc/native-kit 0.43.0 → 0.44.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 +2 -2
- package/src/core/NativeKit.ts +6 -2
- package/src/index.ts +2 -0
- package/src/modules/notifications.ts +6 -0
- package/src/modules/shareTarget.ts +93 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livx.cc/native-kit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Isomorphic native-capabilities kit for PWAs
|
|
3
|
+
"version": "0.44.1",
|
|
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",
|
|
7
7
|
"homepage": "https://github.com/Livshitz/appwrap#readme",
|
package/src/core/NativeKit.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { KitModuleRegistry } from './module-registry';
|
|
|
5
5
|
import { Capability, Handshake, InvokeOptions, KIT_PROTOCOL, KitError, NativeKitAdapter, Platform, Unsubscribe } from './types';
|
|
6
6
|
import { AppModule } from '../modules/app';
|
|
7
7
|
import { AppleSignInModule } from '../modules/appleSignIn';
|
|
8
|
+
import { ShareTargetModule } from '../modules/shareTarget';
|
|
8
9
|
import { BackgroundTaskModule } from '../modules/backgroundTask';
|
|
9
10
|
import { BiometricsModule } from '../modules/biometrics';
|
|
10
11
|
import { BrowserModule } from '../modules/browser';
|
|
@@ -107,11 +108,12 @@ export class NativeKit {
|
|
|
107
108
|
public readonly backgroundTask = new BackgroundTaskModule(this);
|
|
108
109
|
public readonly tracking = new TrackingModule(this);
|
|
109
110
|
public readonly appleSignIn = new AppleSignInModule(this);
|
|
111
|
+
public readonly shareTarget = new ShareTargetModule(this);
|
|
110
112
|
|
|
111
113
|
/**
|
|
112
114
|
* Additive, string-keyed registry mirroring the eager fields above (SAME instances) — a new
|
|
113
115
|
* capability-access path out-of-repo module packs register into (e.g. the billing/health/widget
|
|
114
|
-
*
|
|
116
|
+
* out-of-tree packs call `kit.modules.registerModule(...)`). For core modules `kit.getModule('haptics')
|
|
115
117
|
* === kit.haptics`. Fails loud on miss.
|
|
116
118
|
*/
|
|
117
119
|
public readonly modules = new ModuleRegistry();
|
|
@@ -140,7 +142,7 @@ export class NativeKit {
|
|
|
140
142
|
this.options = { ...new NativeKitOptions(), ...options };
|
|
141
143
|
// Mirror the eager CORE fields into the string-keyed registry (same instances, no behaviour
|
|
142
144
|
// change). Typed as a plain string map — not `Record<keyof KitModuleRegistry, unknown>` — because
|
|
143
|
-
// out-of-repo packs augment KitModuleRegistry with keys
|
|
145
|
+
// out-of-repo packs augment KitModuleRegistry with keys the core never mounts (billing/health/widget);
|
|
144
146
|
// those are registered by the pack's register<Name>Kit(), not here.
|
|
145
147
|
const eager: Record<string, unknown> = {
|
|
146
148
|
haptics: this.haptics, share: this.share, screen: this.screen, keyboard: this.keyboard,
|
|
@@ -152,6 +154,7 @@ export class NativeKit {
|
|
|
152
154
|
speech: this.speech, calendar: this.calendar, app: this.app, browser: this.browser,
|
|
153
155
|
oauth: this.oauth, updates: this.updates,
|
|
154
156
|
backgroundTask: this.backgroundTask, tracking: this.tracking, appleSignIn: this.appleSignIn,
|
|
157
|
+
shareTarget: this.shareTarget,
|
|
155
158
|
};
|
|
156
159
|
for (const [name, inst] of Object.entries(eager)) this.modules.registerModule(name, inst);
|
|
157
160
|
}
|
|
@@ -378,6 +381,7 @@ declare module './module-registry' {
|
|
|
378
381
|
backgroundTask: BackgroundTaskModule;
|
|
379
382
|
tracking: TrackingModule;
|
|
380
383
|
appleSignIn: AppleSignInModule;
|
|
384
|
+
shareTarget: ShareTargetModule;
|
|
381
385
|
}
|
|
382
386
|
}
|
|
383
387
|
|
package/src/index.ts
CHANGED
|
@@ -53,4 +53,6 @@ export type {
|
|
|
53
53
|
} from './modules/appleSignIn';
|
|
54
54
|
export { isAppleSignInResult } from './modules/appleSignIn';
|
|
55
55
|
export { BackgroundTaskModule } from './modules/backgroundTask';
|
|
56
|
+
export { ShareTargetModule, parseSharePayload } from './modules/shareTarget';
|
|
57
|
+
export type { SharedPayload } from './modules/shareTarget';
|
|
56
58
|
export type { BackgroundTaskHandler, ScheduleBackgroundTaskOptions } from './modules/backgroundTask';
|
|
@@ -30,6 +30,12 @@ export interface ScheduleOptions {
|
|
|
30
30
|
* badge unchanged. Set 0 to clear on delivery.
|
|
31
31
|
*/
|
|
32
32
|
badge?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Deliver WITHOUT the alert sound. Default (omitted/false) plays the system alert sound on
|
|
35
|
+
* both platforms — a scheduled notification is an alert, and a silent one is almost never
|
|
36
|
+
* what the caller meant. Android honours this on API 29+ only.
|
|
37
|
+
*/
|
|
38
|
+
silent?: boolean;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
export class NotificationsModule {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { NativeKit } from '../core/NativeKit';
|
|
2
|
+
import type { Unsubscribe } from '../core/types';
|
|
3
|
+
|
|
4
|
+
/** A payload shared INTO the app from the OS share sheet. */
|
|
5
|
+
export interface SharedPayload {
|
|
6
|
+
/** Shared text / URL (Android EXTRA_TEXT). */
|
|
7
|
+
text?: string;
|
|
8
|
+
/** Share subject/title (Android EXTRA_SUBJECT), when the sender set one. */
|
|
9
|
+
title?: string;
|
|
10
|
+
/** Shared files (images), copied by the shell into the app cache — each entry is a path relative
|
|
11
|
+
* to the cache root, readable via `kit.fs.read(p, { dir: 'cache', encoding: 'base64' })`. */
|
|
12
|
+
files?: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse a shareTarget deep link (`<scheme>://share?text=…&title=…&file=…&file=…`) into a payload.
|
|
17
|
+
* Returns null for any URL that isn't a share delivery — safe to feed every deep link through.
|
|
18
|
+
*/
|
|
19
|
+
export function parseSharePayload(url: string | null | undefined): SharedPayload | null {
|
|
20
|
+
// Custom-scheme `<scheme>://share?…` only — http(s) URLs are ordinary web links, never a share delivery.
|
|
21
|
+
if (!url || !/^(?!https?:)[a-z][a-z0-9.+-]*:\/\/share(\?|$)/i.test(url)) return null;
|
|
22
|
+
const q = new URLSearchParams(url.split('?')[1] ?? '');
|
|
23
|
+
const payload: SharedPayload = {};
|
|
24
|
+
const text = q.get('text');
|
|
25
|
+
if (text) payload.text = text;
|
|
26
|
+
const title = q.get('title');
|
|
27
|
+
if (title) payload.title = title;
|
|
28
|
+
const files = q.getAll('file');
|
|
29
|
+
if (files.length) payload.files = files;
|
|
30
|
+
return Object.keys(payload).length ? payload : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Inbound share-target (`shareTarget` module — opt-in): receive content shared TO the app from the
|
|
35
|
+
* OS share sheet. Delivery rides the deep-link path — the shell synthesizes
|
|
36
|
+
* `<urlScheme>://share?text=…&title=…&file=…` for both cold launches (handshake deep link) and warm
|
|
37
|
+
* shares (deeplink.open) — so this module is a thin typed parser over `kit.lifecycle`.
|
|
38
|
+
*
|
|
39
|
+
* Capability (honest): Android + iOS `'native'` — Android via an ACTION_SEND intent-filter on the
|
|
40
|
+
* main activity, iOS via a generated `AppwrapShare` share-extension target that forwards over the
|
|
41
|
+
* app's `urlScheme` (config REQUIRED) with images crossing through the App Group container; web
|
|
42
|
+
* `'none'` (no Web Share Target wiring).
|
|
43
|
+
*/
|
|
44
|
+
export class ShareTargetModule {
|
|
45
|
+
private launchConsumed = false;
|
|
46
|
+
|
|
47
|
+
constructor(private kit: NativeKit) {}
|
|
48
|
+
|
|
49
|
+
get capability() {
|
|
50
|
+
return this.kit.capability('shareTarget');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Subscribe to inbound shares. Fires for warm shares immediately; the COLD-START share (the app was
|
|
55
|
+
* launched by the share) is replayed once to the first subscriber after `kit.ready()` resolves —
|
|
56
|
+
* subscribe early (right after boot) to catch it.
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* Publish the app's SHARE CONTEXT — a small string KV the iOS share extension can read (App Group
|
|
60
|
+
* `appwrap-share-context`) to complete a share directly against the app's backend (the config's
|
|
61
|
+
* `shareTarget.directSync` lane resolves its `{key}` template placeholders from this KV). The
|
|
62
|
+
* framework treats the KV as opaque — publish whatever your sync endpoint needs (e.g. `{ binId }`)
|
|
63
|
+
* and re-publish whenever it changes. `null` clears it (direct sync falls back to the mailbox).
|
|
64
|
+
* Rejects (`UNSUPPORTED`) on the web / on shells that predate the method — treat it as
|
|
65
|
+
* fire-and-forget with a `.catch` (the mailbox path needs no context).
|
|
66
|
+
*/
|
|
67
|
+
setContext(context: Record<string, string | number> | null): Promise<void> {
|
|
68
|
+
return this.kit.invoke('shareTarget.setContext', { context });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onReceive(cb: (payload: SharedPayload) => void): Unsubscribe {
|
|
72
|
+
let cancelled = false;
|
|
73
|
+
void this.kit
|
|
74
|
+
.ready()
|
|
75
|
+
.then(() => {
|
|
76
|
+
if (cancelled || this.launchConsumed) return;
|
|
77
|
+
const payload = parseSharePayload(this.kit.handshakeInfo?.deepLink);
|
|
78
|
+
if (payload) {
|
|
79
|
+
this.launchConsumed = true; // read-once: a second subscriber shouldn't re-ingest it
|
|
80
|
+
cb(payload);
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
.catch(() => { /* web/no-shell: no handshake, nothing to replay */ });
|
|
84
|
+
const un = this.kit.on('deeplink.open', (p) => {
|
|
85
|
+
const payload = parseSharePayload((p as { url: string }).url);
|
|
86
|
+
if (payload) cb(payload);
|
|
87
|
+
});
|
|
88
|
+
return () => {
|
|
89
|
+
cancelled = true;
|
|
90
|
+
un();
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|