@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 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. The package keeps it as a development dependency for its
23
- own native-module build, so installing `@lynxship/expo` does not require a
24
- second manual native dependency in the application.
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
- lynxView = LynxViewBuilder().setTemplateProvider(templateProvider).build(applicationContext)
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
- return withLynxShipPodfile(result, options);
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
@@ -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;CAC1B;AAKD,wBAAgB,0BAA0B,CACxC,KAAK,GAAE,kBAAuB,GAC7B,kBAAkB,CAgDpB"}
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.6",
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-ios": "0.1.0",
53
- "@lynxship/sdk-android": "0.1.0"
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" &&
@@ -1,5 +1,15 @@
1
1
  declare module "react" {
2
- export type ReactNode = unknown;
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;