@webview-bridge/web 1.2.0 → 1.3.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.
@@ -22,6 +22,7 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  MethodNotFoundError: () => MethodNotFoundError,
24
24
  NativeMethodError: () => NativeMethodError,
25
+ linkBridge: () => linkBridge,
25
26
  linkNativeMethod: () => linkNativeMethod,
26
27
  registerWebMethod: () => registerWebMethod
27
28
  });
@@ -89,6 +90,63 @@ var createRandomId = (size = ID_LENGTH) => {
89
90
  return randomValues.join("");
90
91
  };
91
92
 
93
+ // ../../shared/util/src/equals.ts
94
+ var equals = (a, b) => {
95
+ if (a === b) {
96
+ return true;
97
+ }
98
+ if (a && b && typeof a === "object" && typeof b === "object") {
99
+ const arrA = Array.isArray(a);
100
+ const arrB = Array.isArray(b);
101
+ let i;
102
+ let length;
103
+ let key;
104
+ if (arrA && arrB) {
105
+ length = a.length;
106
+ if (length !== b.length) {
107
+ return false;
108
+ }
109
+ for (i = length; i-- !== 0; ) {
110
+ if (!equals(a[i], b[i])) {
111
+ return false;
112
+ }
113
+ }
114
+ return true;
115
+ }
116
+ if (arrA !== arrB) {
117
+ return false;
118
+ }
119
+ const keys = Object.keys(a);
120
+ length = keys.length;
121
+ if (length !== Object.keys(b).length) {
122
+ return false;
123
+ }
124
+ for (i = length; i-- !== 0; ) {
125
+ if (!Object.prototype.hasOwnProperty.call(b, keys[i])) {
126
+ return false;
127
+ }
128
+ }
129
+ for (i = length; i-- !== 0; ) {
130
+ key = keys[i];
131
+ if (!equals(a[key], b[key])) {
132
+ return false;
133
+ }
134
+ }
135
+ return true;
136
+ }
137
+ return a !== a && b !== b;
138
+ };
139
+
140
+ // ../../shared/util/src/removeUndefinedKeys.tsx
141
+ var removeUndefinedKeys = (obj) => {
142
+ Object.keys(obj).forEach((key) => {
143
+ if (obj[key] === void 0) {
144
+ delete obj[key];
145
+ }
146
+ });
147
+ return obj;
148
+ };
149
+
92
150
  // ../../shared/util/src/timeout.ts
93
151
  var timeout = (ms, throwOnError = true) => {
94
152
  return new Promise((resolve, reject) => {
@@ -102,8 +160,56 @@ var timeout = (ms, throwOnError = true) => {
102
160
  });
103
161
  };
104
162
 
105
- // src/linkNativeMethod.ts
163
+ // src/emitter.ts
106
164
  var emitter = createEvents();
165
+
166
+ // src/linkBridgeStore.ts
167
+ var linkBridgeStore = (initialState = {}) => {
168
+ if (!window.ReactNativeWebView) {
169
+ console.warn("[WebViewBridge] Not in a WebView environment");
170
+ }
171
+ if (!window.nativeEmitter) {
172
+ window.nativeEmitter = emitter;
173
+ }
174
+ const getState = () => state;
175
+ const setState = (newState) => {
176
+ const _newState = {
177
+ ...state,
178
+ ...removeUndefinedKeys(newState)
179
+ };
180
+ if (equals(state, _newState)) {
181
+ return;
182
+ }
183
+ const prevState = state;
184
+ state = _newState;
185
+ emitChange(state, prevState);
186
+ };
187
+ emitter.on("bridgeStateChange", (data) => {
188
+ setState(data);
189
+ });
190
+ window.ReactNativeWebView?.postMessage(
191
+ JSON.stringify({
192
+ type: "getBridgeState"
193
+ })
194
+ );
195
+ let state = { ...initialState, ...window.__bridgeInitialState__ };
196
+ const listeners = /* @__PURE__ */ new Set();
197
+ const emitChange = (newState, prevState) => {
198
+ for (const listener of listeners) {
199
+ listener(newState, prevState);
200
+ }
201
+ };
202
+ const subscribe = (listener) => {
203
+ listeners.add(listener);
204
+ return () => listeners.delete(listener);
205
+ };
206
+ return {
207
+ getState,
208
+ subscribe
209
+ };
210
+ };
211
+
212
+ // src/linkBridge.ts
107
213
  var createNativeMethod = (methodName, timeoutMs, throwOnError) => (...args) => {
108
214
  const eventId = createRandomId();
109
215
  return Promise.race([
@@ -128,7 +234,7 @@ var createNativeMethod = (methodName, timeoutMs, throwOnError) => (...args) => {
128
234
  timeout(timeoutMs, throwOnError)
129
235
  ]);
130
236
  };
131
- var linkNativeMethod = (options = {
237
+ var linkBridge = (options = {
132
238
  timeout: 2e3,
133
239
  throwOnError: false
134
240
  }) => {
@@ -159,12 +265,7 @@ var linkNativeMethod = (options = {
159
265
  )
160
266
  };
161
267
  },
162
- {
163
- isWebViewBridgeAvailable: Boolean(window.ReactNativeWebView) && bridgeMethods.length > 0,
164
- isNativeMethodAvailable(methodName) {
165
- return typeof methodName === "string" && Boolean(window.ReactNativeWebView) && bridgeMethods.includes(methodName);
166
- }
167
- }
268
+ {}
168
269
  );
169
270
  const loose = new Proxy(target, {
170
271
  get: (target2, methodName) => {
@@ -180,7 +281,14 @@ var linkNativeMethod = (options = {
180
281
  );
181
282
  }
182
283
  });
183
- Object.assign(target, { loose });
284
+ Object.assign(target, {
285
+ loose,
286
+ store: linkBridgeStore(target),
287
+ isWebViewBridgeAvailable: Boolean(window.ReactNativeWebView) && bridgeMethods.length > 0,
288
+ isNativeMethodAvailable(methodName) {
289
+ return typeof methodName === "string" && Boolean(window.ReactNativeWebView) && bridgeMethods.includes(methodName);
290
+ }
291
+ });
184
292
  const proxy = new Proxy(target, {
185
293
  get: (target2, methodName) => {
186
294
  if (methodName in target2) {
@@ -209,6 +317,14 @@ var linkNativeMethod = (options = {
209
317
  return proxy;
210
318
  };
211
319
 
320
+ // src/linkNativeMethod.ts
321
+ var linkNativeMethod = (options = {
322
+ timeout: 2e3,
323
+ throwOnError: false
324
+ }) => {
325
+ return linkBridge(options);
326
+ };
327
+
212
328
  // src/registerWebMethod.ts
213
329
  var registerWebMethod = (bridge) => {
214
330
  if (!window.ReactNativeWebView) {
@@ -260,6 +376,7 @@ var registerWebMethod = (bridge) => {
260
376
  0 && (module.exports = {
261
377
  MethodNotFoundError,
262
378
  NativeMethodError,
379
+ linkBridge,
263
380
  linkNativeMethod,
264
381
  registerWebMethod
265
382
  });
@@ -60,6 +60,63 @@ var createRandomId = (size = ID_LENGTH) => {
60
60
  return randomValues.join("");
61
61
  };
62
62
 
63
+ // ../../shared/util/src/equals.ts
64
+ var equals = (a, b) => {
65
+ if (a === b) {
66
+ return true;
67
+ }
68
+ if (a && b && typeof a === "object" && typeof b === "object") {
69
+ const arrA = Array.isArray(a);
70
+ const arrB = Array.isArray(b);
71
+ let i;
72
+ let length;
73
+ let key;
74
+ if (arrA && arrB) {
75
+ length = a.length;
76
+ if (length !== b.length) {
77
+ return false;
78
+ }
79
+ for (i = length; i-- !== 0; ) {
80
+ if (!equals(a[i], b[i])) {
81
+ return false;
82
+ }
83
+ }
84
+ return true;
85
+ }
86
+ if (arrA !== arrB) {
87
+ return false;
88
+ }
89
+ const keys = Object.keys(a);
90
+ length = keys.length;
91
+ if (length !== Object.keys(b).length) {
92
+ return false;
93
+ }
94
+ for (i = length; i-- !== 0; ) {
95
+ if (!Object.prototype.hasOwnProperty.call(b, keys[i])) {
96
+ return false;
97
+ }
98
+ }
99
+ for (i = length; i-- !== 0; ) {
100
+ key = keys[i];
101
+ if (!equals(a[key], b[key])) {
102
+ return false;
103
+ }
104
+ }
105
+ return true;
106
+ }
107
+ return a !== a && b !== b;
108
+ };
109
+
110
+ // ../../shared/util/src/removeUndefinedKeys.tsx
111
+ var removeUndefinedKeys = (obj) => {
112
+ Object.keys(obj).forEach((key) => {
113
+ if (obj[key] === void 0) {
114
+ delete obj[key];
115
+ }
116
+ });
117
+ return obj;
118
+ };
119
+
63
120
  // ../../shared/util/src/timeout.ts
64
121
  var timeout = (ms, throwOnError = true) => {
65
122
  return new Promise((resolve, reject) => {
@@ -73,8 +130,56 @@ var timeout = (ms, throwOnError = true) => {
73
130
  });
74
131
  };
75
132
 
76
- // src/linkNativeMethod.ts
133
+ // src/emitter.ts
77
134
  var emitter = createEvents();
135
+
136
+ // src/linkBridgeStore.ts
137
+ var linkBridgeStore = (initialState = {}) => {
138
+ if (!window.ReactNativeWebView) {
139
+ console.warn("[WebViewBridge] Not in a WebView environment");
140
+ }
141
+ if (!window.nativeEmitter) {
142
+ window.nativeEmitter = emitter;
143
+ }
144
+ const getState = () => state;
145
+ const setState = (newState) => {
146
+ const _newState = {
147
+ ...state,
148
+ ...removeUndefinedKeys(newState)
149
+ };
150
+ if (equals(state, _newState)) {
151
+ return;
152
+ }
153
+ const prevState = state;
154
+ state = _newState;
155
+ emitChange(state, prevState);
156
+ };
157
+ emitter.on("bridgeStateChange", (data) => {
158
+ setState(data);
159
+ });
160
+ window.ReactNativeWebView?.postMessage(
161
+ JSON.stringify({
162
+ type: "getBridgeState"
163
+ })
164
+ );
165
+ let state = { ...initialState, ...window.__bridgeInitialState__ };
166
+ const listeners = /* @__PURE__ */ new Set();
167
+ const emitChange = (newState, prevState) => {
168
+ for (const listener of listeners) {
169
+ listener(newState, prevState);
170
+ }
171
+ };
172
+ const subscribe = (listener) => {
173
+ listeners.add(listener);
174
+ return () => listeners.delete(listener);
175
+ };
176
+ return {
177
+ getState,
178
+ subscribe
179
+ };
180
+ };
181
+
182
+ // src/linkBridge.ts
78
183
  var createNativeMethod = (methodName, timeoutMs, throwOnError) => (...args) => {
79
184
  const eventId = createRandomId();
80
185
  return Promise.race([
@@ -99,7 +204,7 @@ var createNativeMethod = (methodName, timeoutMs, throwOnError) => (...args) => {
99
204
  timeout(timeoutMs, throwOnError)
100
205
  ]);
101
206
  };
102
- var linkNativeMethod = (options = {
207
+ var linkBridge = (options = {
103
208
  timeout: 2e3,
104
209
  throwOnError: false
105
210
  }) => {
@@ -130,12 +235,7 @@ var linkNativeMethod = (options = {
130
235
  )
131
236
  };
132
237
  },
133
- {
134
- isWebViewBridgeAvailable: Boolean(window.ReactNativeWebView) && bridgeMethods.length > 0,
135
- isNativeMethodAvailable(methodName) {
136
- return typeof methodName === "string" && Boolean(window.ReactNativeWebView) && bridgeMethods.includes(methodName);
137
- }
138
- }
238
+ {}
139
239
  );
140
240
  const loose = new Proxy(target, {
141
241
  get: (target2, methodName) => {
@@ -151,7 +251,14 @@ var linkNativeMethod = (options = {
151
251
  );
152
252
  }
153
253
  });
154
- Object.assign(target, { loose });
254
+ Object.assign(target, {
255
+ loose,
256
+ store: linkBridgeStore(target),
257
+ isWebViewBridgeAvailable: Boolean(window.ReactNativeWebView) && bridgeMethods.length > 0,
258
+ isNativeMethodAvailable(methodName) {
259
+ return typeof methodName === "string" && Boolean(window.ReactNativeWebView) && bridgeMethods.includes(methodName);
260
+ }
261
+ });
155
262
  const proxy = new Proxy(target, {
156
263
  get: (target2, methodName) => {
157
264
  if (methodName in target2) {
@@ -180,6 +287,14 @@ var linkNativeMethod = (options = {
180
287
  return proxy;
181
288
  };
182
289
 
290
+ // src/linkNativeMethod.ts
291
+ var linkNativeMethod = (options = {
292
+ timeout: 2e3,
293
+ throwOnError: false
294
+ }) => {
295
+ return linkBridge(options);
296
+ };
297
+
183
298
  // src/registerWebMethod.ts
184
299
  var registerWebMethod = (bridge) => {
185
300
  if (!window.ReactNativeWebView) {
@@ -230,6 +345,7 @@ var registerWebMethod = (bridge) => {
230
345
  export {
231
346
  MethodNotFoundError,
232
347
  NativeMethodError,
348
+ linkBridge,
233
349
  linkNativeMethod,
234
350
  registerWebMethod
235
351
  };
@@ -0,0 +1 @@
1
+ export declare const emitter: import("../../../shared/util/src").EventEmitter<import("../../../shared/util/src").DefaultEvents>;
@@ -1,4 +1,6 @@
1
1
  export * from "./error";
2
+ export * from "./linkBridge";
2
3
  export * from "./linkNativeMethod";
3
4
  export * from "./registerWebMethod";
4
5
  export type * from "./types";
6
+ export * from "../../../shared/util/src/types";
@@ -0,0 +1,9 @@
1
+ import type { Bridge, BridgeStore, ExcludePrimitive, ExtractStore } from "../../../shared/util/src/types";
2
+ import { LinkBridge } from "./types";
3
+ export interface LinkBridgeOptions<T extends BridgeStore<T extends Bridge ? T : any>> {
4
+ timeout?: number;
5
+ throwOnError?: boolean | (keyof ExtractStore<T>)[] | string[];
6
+ onFallback?: (methodName: string) => void;
7
+ onReady?: (method: LinkBridge<ExcludePrimitive<ExtractStore<T>>, Omit<T, "setState">>) => void;
8
+ }
9
+ export declare const linkBridge: <T extends BridgeStore<T extends Bridge ? T : any>>(options?: LinkBridgeOptions<T>) => LinkBridge<ExcludePrimitive<ExtractStore<T>>, Omit<T, "setState">>;
@@ -0,0 +1,6 @@
1
+ import { Bridge, BridgeStore, OnlyJSON } from "../../../shared/util/src/types";
2
+ export type Store<BridgeObject extends Bridge> = ({ get, set, }: {
3
+ get: () => BridgeObject;
4
+ set: (newState: Partial<OnlyJSON<BridgeObject>>) => void;
5
+ }) => BridgeObject;
6
+ export declare const linkBridgeStore: <T extends BridgeStore<T extends Bridge ? T : any>>(initialState?: Partial<T>) => Omit<T, "setState">;
@@ -1,8 +1,7 @@
1
- import { Bridge, NativeMethod } from "./types";
2
- export interface LinkNativeMethodOptions<BridgeObject extends Bridge> {
3
- timeout?: number;
4
- throwOnError?: boolean | (keyof BridgeObject)[] | string[];
5
- onFallback?: (methodName: string) => void;
6
- onReady?: (method: NativeMethod<BridgeObject>) => void;
7
- }
8
- export declare const linkNativeMethod: <BridgeObject extends Bridge>(options?: LinkNativeMethodOptions<BridgeObject>) => NativeMethod<BridgeObject>;
1
+ import type { Bridge, BridgeStore, ExcludePrimitive, ExtractStore } from "../../../shared/util/src/types";
2
+ import { LinkBridgeOptions } from "./linkBridge";
3
+ import { LinkBridge } from "./types";
4
+ /**
5
+ * @deprecated Use `linkBridge` instead. It's just renamed to `linkBridge`.
6
+ */
7
+ export declare const linkNativeMethod: <T extends BridgeStore<T extends Bridge ? T : any>>(options?: LinkBridgeOptions<T>) => LinkBridge<ExcludePrimitive<ExtractStore<T>>, Omit<T, "setState">>;
@@ -1,2 +1,2 @@
1
- import type { Bridge } from "./types";
2
- export declare const registerWebMethod: <BridgeObject extends Bridge>(bridge: BridgeObject) => BridgeObject;
1
+ import type { WebBridge } from "./types";
2
+ export declare const registerWebMethod: <BridgeObject extends WebBridge>(bridge: BridgeObject) => BridgeObject;
@@ -1,9 +1,10 @@
1
- export type AsyncFunction = (...args: any[]) => Promise<any>;
2
- export type Bridge = Record<string, AsyncFunction>;
3
- export type NativeMethod<T> = {
1
+ import { AsyncFunction } from "../../../../shared/util/src/types";
2
+ export type WebBridge = Record<string, AsyncFunction>;
3
+ export type LinkBridge<T, U> = {
4
4
  isWebViewBridgeAvailable: boolean;
5
5
  isNativeMethodAvailable(method: keyof T): boolean;
6
6
  isNativeMethodAvailable(method: string): boolean;
7
+ store: U;
7
8
  loose: {
8
9
  [K in keyof T]: (...args: any[]) => Promise<any>;
9
10
  } & {
@@ -0,0 +1 @@
1
+ export declare const equals: (a: any, b: any) => boolean;
@@ -1,4 +1,6 @@
1
1
  export * from "./createEvents";
2
2
  export * from "./createRandomId";
3
+ export * from "./equals";
3
4
  export * from "./noop";
5
+ export * from "./removeUndefinedKeys";
4
6
  export * from "./timeout";
@@ -0,0 +1 @@
1
+ export declare const removeUndefinedKeys: (obj: Record<string, any>) => Record<string, any>;
@@ -0,0 +1,23 @@
1
+ export type AsyncFunction = (...args: any[]) => Promise<any>;
2
+ export type Primitive = string | number | boolean | null | undefined;
3
+ export type RawJSON = Primitive | {
4
+ [key: string]: RawJSON;
5
+ } | RawJSONArray;
6
+ interface RawJSONArray extends Array<RawJSON> {
7
+ }
8
+ export type Bridge = Record<string, AsyncFunction | RawJSON>;
9
+ export type BridgeStore<T extends Bridge> = {
10
+ getState: () => T;
11
+ setState: (newState: Partial<OnlyJSON<T>>) => void;
12
+ subscribe: (listener: (newState: T, prevState: T) => void) => () => void;
13
+ };
14
+ export type ExtractStore<S> = S extends {
15
+ getState: () => infer T;
16
+ } ? T : never;
17
+ export type OnlyJSON<T> = {
18
+ [P in keyof T as T[P] extends RawJSON ? P : never]: T[P];
19
+ };
20
+ export type ExcludePrimitive<T> = {
21
+ [P in keyof T as T[P] extends RawJSON ? never : P]: T[P];
22
+ };
23
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@webview-bridge/web",
3
3
  "type": "module",
4
- "version": "1.2.0",
4
+ "version": "1.3.1",
5
5
  "description": "Fully Type-Safe Integration for React Native WebView and Web",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -34,7 +34,7 @@
34
34
  "esbuild": "^0.19.4"
35
35
  },
36
36
  "scripts": {
37
- "build": "node esbuild.config.js && tsc --emitDeclarationOnly",
37
+ "build": "node esbuild.config.js && tspc --emitDeclarationOnly",
38
38
  "test:type": "tsc --noEmit"
39
39
  }
40
40
  }