@lynxship/expo 0.1.6 → 0.1.7
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 +36 -3
- package/android/src/main/java/com/lynxship/expo/LynxShipExpoView.kt +3 -1
- package/app.plugin.cjs +58 -1
- package/asset-sync.cjs.d.cts +43 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -0
- package/package.json +5 -3
- package/src/config.ts +36 -0
- package/src/externals.d.ts +11 -1
package/README.md
CHANGED
|
@@ -19,9 +19,10 @@ npx pod-install
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
`@lynxship/expo` uses the `expo-modules-core` implementation supplied by the
|
|
22
|
-
installed Expo SDK.
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
installed Expo SDK. It is declared as a peer dependency so Expo Doctor can
|
|
23
|
+
validate the application explicitly; `npx expo install @lynxship/expo` should
|
|
24
|
+
select the SDK-compatible version. The workspace keeps a matching development
|
|
25
|
+
dependency for its own native-module build.
|
|
25
26
|
|
|
26
27
|
When the project uses a static `app.json` or `app.config.json`, the Expo CLI
|
|
27
28
|
automatically adds `@lynxship/expo` to `expo.plugins` during `npx expo install`.
|
|
@@ -43,6 +44,12 @@ options are needed, add the config plugin manually:
|
|
|
43
44
|
"projectId": "00000000-0000-4000-8000-000000000000",
|
|
44
45
|
"channel": "production",
|
|
45
46
|
"runtimeVersion": "lynx-runtime-2026-01",
|
|
47
|
+
"notifications": {
|
|
48
|
+
"enabled": true,
|
|
49
|
+
"enableBackgroundRemoteNotifications": true,
|
|
50
|
+
"communicationNotifications": true,
|
|
51
|
+
"android": { "defaultChannel": "default" }
|
|
52
|
+
},
|
|
46
53
|
"publicKeys": {
|
|
47
54
|
"release-key-1": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
|
48
55
|
},
|
|
@@ -90,6 +97,32 @@ you intentionally operate a pinned native compatibility lane; do not put
|
|
|
90
97
|
`latest` in a production lockfile without reviewing the resulting native
|
|
91
98
|
build.
|
|
92
99
|
|
|
100
|
+
## Optional push notifications
|
|
101
|
+
|
|
102
|
+
When an Expo app also uses `@lynxship/notifications/expo`, set
|
|
103
|
+
`notifications.enabled` to `true` in this plugin. LynxShip delegates the
|
|
104
|
+
native permission, FCM/APNs token acquisition and build-time configuration to
|
|
105
|
+
the official `expo-notifications` package, then the JavaScript adapter sends
|
|
106
|
+
the token to the authenticated backend. Install the SDK-selected native
|
|
107
|
+
package first:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx expo install expo-notifications expo-constants
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The app must call `LynxShipNotifications.register(...)` with its authenticated
|
|
114
|
+
user/project identity and HTTPS registration endpoint. This is intentionally
|
|
115
|
+
not guessed by the plugin: app identity and backend authorization are
|
|
116
|
+
application data. FCM/APNs credentials also remain in EAS/CI secrets.
|
|
117
|
+
|
|
118
|
+
For message or presence notifications with profile images, pass an HTTPS
|
|
119
|
+
`imageUrl` from the backend payload. Android can render the image directly
|
|
120
|
+
through FCM. iOS requires a separate Notification Service Extension target;
|
|
121
|
+
use the template published by `@lynxship/notifications` and add it to the Expo
|
|
122
|
+
iOS project before an EAS build. The extension is a separately signed native
|
|
123
|
+
target, so it cannot be created safely by the JavaScript `LynxView` component
|
|
124
|
+
at runtime.
|
|
125
|
+
|
|
93
126
|
## Use the view
|
|
94
127
|
|
|
95
128
|
```tsx
|
|
@@ -55,7 +55,9 @@ class LynxShipExpoView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
55
55
|
|
|
56
56
|
init {
|
|
57
57
|
initializeLynx(context)
|
|
58
|
-
|
|
58
|
+
// The Lynx view must be created with the host/view context. Keep the
|
|
59
|
+
// application context only for process-wide services and storage.
|
|
60
|
+
lynxView = LynxViewBuilder().setTemplateProvider(templateProvider).build(context)
|
|
59
61
|
lynxView.addLynxViewClient(object : LynxViewClient() {
|
|
60
62
|
override fun onFirstScreen() {
|
|
61
63
|
try {
|
package/app.plugin.cjs
CHANGED
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
withSettingsGradle,
|
|
8
8
|
withXcodeProject,
|
|
9
9
|
IOSConfig,
|
|
10
|
+
withPlugins,
|
|
10
11
|
} = require("@expo/config-plugins");
|
|
11
12
|
const path = require("node:path");
|
|
12
13
|
const { syncLynxAssets } = require("./asset-sync.cjs");
|
|
@@ -78,6 +79,31 @@ function assertOptions(options) {
|
|
|
78
79
|
typeof options.syncBundle !== "boolean"
|
|
79
80
|
)
|
|
80
81
|
throw new Error("@lynxship/expo syncBundle must be a boolean");
|
|
82
|
+
if (options.notifications !== undefined) {
|
|
83
|
+
if (
|
|
84
|
+
typeof options.notifications !== "object" ||
|
|
85
|
+
options.notifications === null ||
|
|
86
|
+
typeof options.notifications.enabled !== "boolean"
|
|
87
|
+
)
|
|
88
|
+
throw new Error(
|
|
89
|
+
"@lynxship/expo notifications must be an object with boolean enabled",
|
|
90
|
+
);
|
|
91
|
+
if (
|
|
92
|
+
options.notifications.enableBackgroundRemoteNotifications !== undefined &&
|
|
93
|
+
typeof options.notifications.enableBackgroundRemoteNotifications !==
|
|
94
|
+
"boolean"
|
|
95
|
+
)
|
|
96
|
+
throw new Error(
|
|
97
|
+
"@lynxship/expo enableBackgroundRemoteNotifications must be a boolean",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (
|
|
101
|
+
options.notifications?.communicationNotifications !== undefined &&
|
|
102
|
+
typeof options.notifications.communicationNotifications !== "boolean"
|
|
103
|
+
)
|
|
104
|
+
throw new Error(
|
|
105
|
+
"@lynxship/expo communicationNotifications must be a boolean",
|
|
106
|
+
);
|
|
81
107
|
if (
|
|
82
108
|
options.embeddedBundle !== undefined &&
|
|
83
109
|
(typeof options.embeddedBundle !== "string" ||
|
|
@@ -157,6 +183,14 @@ function withLynxShipInfoPlist(config, options) {
|
|
|
157
183
|
publicKeys: options.publicKeys || {},
|
|
158
184
|
maxReleaseBytes: options.maxReleaseBytes || 104857600,
|
|
159
185
|
};
|
|
186
|
+
if (options.notifications?.communicationNotifications) {
|
|
187
|
+
const activityTypes = Array.isArray(value.modResults.NSUserActivityTypes)
|
|
188
|
+
? value.modResults.NSUserActivityTypes
|
|
189
|
+
: [];
|
|
190
|
+
if (!activityTypes.includes("INSendMessageIntent"))
|
|
191
|
+
activityTypes.push("INSendMessageIntent");
|
|
192
|
+
value.modResults.NSUserActivityTypes = activityTypes;
|
|
193
|
+
}
|
|
160
194
|
return value;
|
|
161
195
|
});
|
|
162
196
|
}
|
|
@@ -286,6 +320,28 @@ function withLynxShipIosAssets(config, options) {
|
|
|
286
320
|
});
|
|
287
321
|
}
|
|
288
322
|
|
|
323
|
+
function withLynxShipNotifications(config, options) {
|
|
324
|
+
const notifications = options.notifications;
|
|
325
|
+
if (!notifications?.enabled) return config;
|
|
326
|
+
try {
|
|
327
|
+
return withPlugins(config, [
|
|
328
|
+
[
|
|
329
|
+
"expo-notifications",
|
|
330
|
+
{
|
|
331
|
+
...(notifications.android || {}),
|
|
332
|
+
enableBackgroundRemoteNotifications:
|
|
333
|
+
notifications.enableBackgroundRemoteNotifications || false,
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
]);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
"@lynxship/expo notifications requires expo-notifications. Install it with `npx expo install expo-notifications`.",
|
|
340
|
+
{ cause: error },
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
289
345
|
function withLynxShipExpo(config, props = {}) {
|
|
290
346
|
const options = assertOptions({ ...getOptions(config), ...props });
|
|
291
347
|
let result = withLynxShipAndroidManifest(config, options);
|
|
@@ -294,7 +350,8 @@ function withLynxShipExpo(config, props = {}) {
|
|
|
294
350
|
result = withLynxShipAndroidSdk(result);
|
|
295
351
|
result = withLynxShipAndroidAssets(result, options);
|
|
296
352
|
result = withLynxShipIosAssets(result, options);
|
|
297
|
-
|
|
353
|
+
result = withLynxShipPodfile(result, options);
|
|
354
|
+
return withLynxShipNotifications(result, options);
|
|
298
355
|
}
|
|
299
356
|
|
|
300
357
|
module.exports = withLynxShipExpo;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
declare const assetSync: {
|
|
2
|
+
createBundlePlan(
|
|
3
|
+
projectRoot: string,
|
|
4
|
+
options?: {
|
|
5
|
+
bundlePath?: string;
|
|
6
|
+
embeddedBundle?: string;
|
|
7
|
+
},
|
|
8
|
+
): Promise<{
|
|
9
|
+
sourceBundle: string;
|
|
10
|
+
sourceDirectory: string;
|
|
11
|
+
bundlePath: string;
|
|
12
|
+
embeddedBundle: string;
|
|
13
|
+
files: Array<{
|
|
14
|
+
absolute: string;
|
|
15
|
+
relative: string;
|
|
16
|
+
destination: string;
|
|
17
|
+
}>;
|
|
18
|
+
}>;
|
|
19
|
+
syncBundleDirectory(options: {
|
|
20
|
+
projectRoot: string;
|
|
21
|
+
plan: Awaited<ReturnType<typeof assetSync.createBundlePlan>>;
|
|
22
|
+
destinationRoot: string;
|
|
23
|
+
manifestPath: string;
|
|
24
|
+
platform: "android" | "ios";
|
|
25
|
+
}): Promise<{
|
|
26
|
+
platform: "android" | "ios";
|
|
27
|
+
sourceBundle: string;
|
|
28
|
+
destinationRoot: string;
|
|
29
|
+
manifestPath: string;
|
|
30
|
+
files: Array<{ path: string; size: number; sha256: string }>;
|
|
31
|
+
}>;
|
|
32
|
+
syncLynxAssets(
|
|
33
|
+
projectRoot: string,
|
|
34
|
+
options?: {
|
|
35
|
+
platform?: "android" | "ios";
|
|
36
|
+
bundlePath?: string;
|
|
37
|
+
embeddedBundle?: string;
|
|
38
|
+
iosSourceRoot?: string;
|
|
39
|
+
},
|
|
40
|
+
): Promise<unknown>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export = assetSync;
|
package/dist/config.d.ts
CHANGED
|
@@ -11,6 +11,17 @@ export interface LynxShipExpoConfig {
|
|
|
11
11
|
syncBundle?: boolean;
|
|
12
12
|
lynxVersion?: string;
|
|
13
13
|
maxReleaseBytes?: number;
|
|
14
|
+
notifications?: {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
enableBackgroundRemoteNotifications?: boolean;
|
|
17
|
+
/** Add the iOS messaging intent declaration for rich communication alerts. */
|
|
18
|
+
communicationNotifications?: boolean;
|
|
19
|
+
android?: {
|
|
20
|
+
icon?: string;
|
|
21
|
+
color?: string;
|
|
22
|
+
defaultChannel?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
14
25
|
}
|
|
15
26
|
export declare function validateLynxShipExpoConfig(value?: LynxShipExpoConfig): LynxShipExpoConfig;
|
|
16
27
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,mCAAmC,CAAC,EAAE,OAAO,CAAC;QAC9C,8EAA8E;QAC9E,0BAA0B,CAAC,EAAE,OAAO,CAAC;QACrC,OAAO,CAAC,EAAE;YACR,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,cAAc,CAAC,EAAE,MAAM,CAAC;SACzB,CAAC;KACH,CAAC;CACH;AAKD,wBAAgB,0BAA0B,CACxC,KAAK,GAAE,kBAAuB,GAC7B,kBAAkB,CAyEpB"}
|
package/dist/config.js
CHANGED
|
@@ -25,6 +25,19 @@ function validateLynxShipExpoConfig(value = {}) {
|
|
|
25
25
|
}
|
|
26
26
|
if (value.syncBundle !== undefined && typeof value.syncBundle !== "boolean")
|
|
27
27
|
throw new Error("LynxShip Expo syncBundle must be a boolean");
|
|
28
|
+
if (value.notifications !== undefined) {
|
|
29
|
+
if (typeof value.notifications !== "object" ||
|
|
30
|
+
value.notifications === null ||
|
|
31
|
+
typeof value.notifications.enabled !== "boolean")
|
|
32
|
+
throw new Error("LynxShip Expo notifications must be an object with boolean enabled");
|
|
33
|
+
if (value.notifications.enableBackgroundRemoteNotifications !== undefined &&
|
|
34
|
+
typeof value.notifications.enableBackgroundRemoteNotifications !==
|
|
35
|
+
"boolean")
|
|
36
|
+
throw new Error("LynxShip Expo enableBackgroundRemoteNotifications must be a boolean");
|
|
37
|
+
if (value.notifications.communicationNotifications !== undefined &&
|
|
38
|
+
typeof value.notifications.communicationNotifications !== "boolean")
|
|
39
|
+
throw new Error("LynxShip Expo communicationNotifications must be a boolean");
|
|
40
|
+
}
|
|
28
41
|
if (value.lynxVersion !== undefined &&
|
|
29
42
|
value.lynxVersion !== "auto" &&
|
|
30
43
|
value.lynxVersion !== "latest" &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynxship/expo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Expo native LynxView with LynxShip OTA delivery and cache support.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"src",
|
|
13
13
|
"app.plugin.js",
|
|
14
14
|
"app.plugin.cjs",
|
|
15
|
+
"asset-sync.cjs.d.cts",
|
|
15
16
|
"asset-sync.cjs",
|
|
16
17
|
"expo-module.config.json",
|
|
17
18
|
"lynxship-expo.podspec",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"peerDependencies": {
|
|
43
44
|
"@expo/config-plugins": ">=9",
|
|
44
45
|
"expo": ">=52",
|
|
46
|
+
"expo-modules-core": ">=52",
|
|
45
47
|
"react": ">=18",
|
|
46
48
|
"react-native": ">=0.76"
|
|
47
49
|
},
|
|
@@ -49,8 +51,8 @@
|
|
|
49
51
|
"expo-modules-core": "57.0.13"
|
|
50
52
|
},
|
|
51
53
|
"dependencies": {
|
|
52
|
-
"@lynxship/sdk-
|
|
53
|
-
"@lynxship/sdk-
|
|
54
|
+
"@lynxship/sdk-android": "0.1.0",
|
|
55
|
+
"@lynxship/sdk-ios": "0.1.0"
|
|
54
56
|
},
|
|
55
57
|
"scripts": {
|
|
56
58
|
"build": "tsc -b tsconfig.json",
|
package/src/config.ts
CHANGED
|
@@ -11,6 +11,17 @@ export interface LynxShipExpoConfig {
|
|
|
11
11
|
syncBundle?: boolean;
|
|
12
12
|
lynxVersion?: string;
|
|
13
13
|
maxReleaseBytes?: number;
|
|
14
|
+
notifications?: {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
enableBackgroundRemoteNotifications?: boolean;
|
|
17
|
+
/** Add the iOS messaging intent declaration for rich communication alerts. */
|
|
18
|
+
communicationNotifications?: boolean;
|
|
19
|
+
android?: {
|
|
20
|
+
icon?: string;
|
|
21
|
+
color?: string;
|
|
22
|
+
defaultChannel?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
14
25
|
}
|
|
15
26
|
|
|
16
27
|
/** Let the native package manager resolve the current Lynx SDK by default. */
|
|
@@ -48,6 +59,31 @@ export function validateLynxShipExpoConfig(
|
|
|
48
59
|
}
|
|
49
60
|
if (value.syncBundle !== undefined && typeof value.syncBundle !== "boolean")
|
|
50
61
|
throw new Error("LynxShip Expo syncBundle must be a boolean");
|
|
62
|
+
if (value.notifications !== undefined) {
|
|
63
|
+
if (
|
|
64
|
+
typeof value.notifications !== "object" ||
|
|
65
|
+
value.notifications === null ||
|
|
66
|
+
typeof value.notifications.enabled !== "boolean"
|
|
67
|
+
)
|
|
68
|
+
throw new Error(
|
|
69
|
+
"LynxShip Expo notifications must be an object with boolean enabled",
|
|
70
|
+
);
|
|
71
|
+
if (
|
|
72
|
+
value.notifications.enableBackgroundRemoteNotifications !== undefined &&
|
|
73
|
+
typeof value.notifications.enableBackgroundRemoteNotifications !==
|
|
74
|
+
"boolean"
|
|
75
|
+
)
|
|
76
|
+
throw new Error(
|
|
77
|
+
"LynxShip Expo enableBackgroundRemoteNotifications must be a boolean",
|
|
78
|
+
);
|
|
79
|
+
if (
|
|
80
|
+
value.notifications.communicationNotifications !== undefined &&
|
|
81
|
+
typeof value.notifications.communicationNotifications !== "boolean"
|
|
82
|
+
)
|
|
83
|
+
throw new Error(
|
|
84
|
+
"LynxShip Expo communicationNotifications must be a boolean",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
51
87
|
if (
|
|
52
88
|
value.lynxVersion !== undefined &&
|
|
53
89
|
value.lynxVersion !== "auto" &&
|
package/src/externals.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
declare module "react" {
|
|
2
|
-
export
|
|
2
|
+
export interface ReactElement {
|
|
3
|
+
readonly type: unknown;
|
|
4
|
+
readonly props: unknown;
|
|
5
|
+
}
|
|
6
|
+
export type ReactNode =
|
|
7
|
+
| ReactElement
|
|
8
|
+
| string
|
|
9
|
+
| number
|
|
10
|
+
| boolean
|
|
11
|
+
| null
|
|
12
|
+
| undefined;
|
|
3
13
|
export type ComponentType<Props = Record<string, unknown>> = (
|
|
4
14
|
props: Props,
|
|
5
15
|
) => ReactNode;
|