@webview-bridge/web 1.0.0
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 +5 -0
- package/dist/commonjs/index.cjs +195 -0
- package/dist/module/index.mjs +166 -0
- package/dist/typescript/packages/web/src/createNativeMethod.d.ts +3 -0
- package/dist/typescript/packages/web/src/createWebMethod.d.ts +2 -0
- package/dist/typescript/packages/web/src/error.d.ts +3 -0
- package/dist/typescript/packages/web/src/index.d.ts +4 -0
- package/dist/typescript/packages/web/src/linkNativeMethod.d.ts +6 -0
- package/dist/typescript/packages/web/src/registerWebMethod.d.ts +2 -0
- package/dist/typescript/packages/web/src/types/index.d.ts +5 -0
- package/dist/typescript/shared/util/src/createEvents.d.ts +16 -0
- package/dist/typescript/shared/util/src/createRandomId.d.ts +1 -0
- package/dist/typescript/shared/util/src/index.d.ts +4 -0
- package/dist/typescript/shared/util/src/noop.d.ts +1 -0
- package/dist/typescript/shared/util/src/timeout.d.ts +1 -0
- package/dist/typescript/shared/util/src/types.d.ts +5 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
MethodNotFoundError: () => MethodNotFoundError,
|
|
24
|
+
linkNativeMethod: () => linkNativeMethod,
|
|
25
|
+
registerWebMethod: () => registerWebMethod
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(src_exports);
|
|
28
|
+
|
|
29
|
+
// src/error.ts
|
|
30
|
+
var MethodNotFoundError = class extends Error {
|
|
31
|
+
constructor(methodName) {
|
|
32
|
+
super(`Method ${methodName} is not defined`);
|
|
33
|
+
this.name = "MethodNotFoundError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ../../shared/util/src/createEvents.ts
|
|
38
|
+
var createEvents = () => ({
|
|
39
|
+
events: {},
|
|
40
|
+
emit(event, ...args) {
|
|
41
|
+
const callbacks = this.events[event] || [];
|
|
42
|
+
for (let i = 0, length = callbacks.length; i < length; i++) {
|
|
43
|
+
callbacks[i](...args);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
on(event, cb) {
|
|
47
|
+
this.events[event]?.push(cb) || (this.events[event] = [cb]);
|
|
48
|
+
return () => {
|
|
49
|
+
this.events[event] = this.events[event]?.filter((i) => cb !== i);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
var createResolver = (emitter2, method, eventId, evaluate) => {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const unbind = emitter2.on(`${method}-${eventId}`, (data) => {
|
|
56
|
+
unbind();
|
|
57
|
+
resolve(data);
|
|
58
|
+
});
|
|
59
|
+
evaluate();
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ../../shared/util/src/createRandomId.tsx
|
|
64
|
+
var TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
65
|
+
var ID_LENGTH = 21;
|
|
66
|
+
var createRandomId = (size = ID_LENGTH) => {
|
|
67
|
+
const randomValues = Array.from(
|
|
68
|
+
{ length: size },
|
|
69
|
+
() => TABLE[Math.floor(Math.random() * TABLE.length)]
|
|
70
|
+
);
|
|
71
|
+
return randomValues.join("");
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ../../shared/util/src/timeout.ts
|
|
75
|
+
var timeout = (ms) => {
|
|
76
|
+
return new Promise((_, reject) => {
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
reject(new Error("Timeout"));
|
|
79
|
+
}, ms);
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/linkNativeMethod.ts
|
|
84
|
+
var emitter = createEvents();
|
|
85
|
+
var linkNativeMethod = (options = {
|
|
86
|
+
timeout: 2e3,
|
|
87
|
+
throwOnError: false
|
|
88
|
+
}) => {
|
|
89
|
+
const {
|
|
90
|
+
timeout: timeoutMs = 2e3,
|
|
91
|
+
throwOnError = false,
|
|
92
|
+
onFallback
|
|
93
|
+
} = options;
|
|
94
|
+
if (!window.ReactNativeWebView) {
|
|
95
|
+
console.warn("[WebViewBridge] Not in a WebView environment");
|
|
96
|
+
}
|
|
97
|
+
const bridgeMethods = window.__bridgeMethods__ ?? [];
|
|
98
|
+
if (!window.nativeEmitter) {
|
|
99
|
+
window.nativeEmitter = emitter;
|
|
100
|
+
}
|
|
101
|
+
const target = bridgeMethods.reduce((acc, method) => {
|
|
102
|
+
return {
|
|
103
|
+
...acc,
|
|
104
|
+
[method]: (...args) => {
|
|
105
|
+
const eventId = createRandomId();
|
|
106
|
+
return Promise.race([
|
|
107
|
+
createResolver(emitter, method, eventId, () => {
|
|
108
|
+
window.ReactNativeWebView?.postMessage(
|
|
109
|
+
JSON.stringify({
|
|
110
|
+
type: "bridge",
|
|
111
|
+
body: {
|
|
112
|
+
method,
|
|
113
|
+
eventId,
|
|
114
|
+
args
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
}),
|
|
119
|
+
timeout(timeoutMs)
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}, {});
|
|
124
|
+
return new Proxy(target, {
|
|
125
|
+
get: (target2, method) => {
|
|
126
|
+
if (method in target2) {
|
|
127
|
+
return target2[method];
|
|
128
|
+
}
|
|
129
|
+
window.ReactNativeWebView?.postMessage(
|
|
130
|
+
JSON.stringify({
|
|
131
|
+
type: "fallback",
|
|
132
|
+
body: {
|
|
133
|
+
method
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
onFallback?.(method);
|
|
138
|
+
if (throwOnError === true) {
|
|
139
|
+
return () => Promise.reject(new MethodNotFoundError(method));
|
|
140
|
+
} else if (Array.isArray(throwOnError) && throwOnError.includes(method)) {
|
|
141
|
+
return () => Promise.reject(new MethodNotFoundError(method));
|
|
142
|
+
} else {
|
|
143
|
+
console.warn(
|
|
144
|
+
`[WebViewBridge] ${method} is not defined, using fallback.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return () => Promise.resolve();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/registerWebMethod.ts
|
|
153
|
+
var registerWebMethod = (bridge) => {
|
|
154
|
+
if (!window.ReactNativeWebView) {
|
|
155
|
+
console.warn("[WebViewBridge] Not in a WebView environment");
|
|
156
|
+
return bridge;
|
|
157
|
+
}
|
|
158
|
+
const bridgeEntries = Object.entries(bridge);
|
|
159
|
+
const bridgeNames = Object.keys(bridge);
|
|
160
|
+
const emitter2 = createEvents();
|
|
161
|
+
window.webEmitter = emitter2;
|
|
162
|
+
for (const [funcName, func] of bridgeEntries) {
|
|
163
|
+
const $func = async (eventId, args) => {
|
|
164
|
+
const value = await func(...args);
|
|
165
|
+
window.ReactNativeWebView?.postMessage(
|
|
166
|
+
JSON.stringify({
|
|
167
|
+
type: "webMethodResponse",
|
|
168
|
+
body: { funcName, eventId, value }
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
emitter2.on(funcName, $func);
|
|
173
|
+
}
|
|
174
|
+
const register = () => {
|
|
175
|
+
window.ReactNativeWebView?.postMessage(
|
|
176
|
+
JSON.stringify({
|
|
177
|
+
type: "registerWebMethod",
|
|
178
|
+
body: { bridgeNames }
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
window.removeEventListener("DOMContentLoaded", register);
|
|
182
|
+
};
|
|
183
|
+
if (!window.ReactNativeWebView) {
|
|
184
|
+
window.addEventListener("DOMContentLoaded", register);
|
|
185
|
+
return bridge;
|
|
186
|
+
}
|
|
187
|
+
register();
|
|
188
|
+
return bridge;
|
|
189
|
+
};
|
|
190
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
191
|
+
0 && (module.exports = {
|
|
192
|
+
MethodNotFoundError,
|
|
193
|
+
linkNativeMethod,
|
|
194
|
+
registerWebMethod
|
|
195
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// src/error.ts
|
|
2
|
+
var MethodNotFoundError = class extends Error {
|
|
3
|
+
constructor(methodName) {
|
|
4
|
+
super(`Method ${methodName} is not defined`);
|
|
5
|
+
this.name = "MethodNotFoundError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// ../../shared/util/src/createEvents.ts
|
|
10
|
+
var createEvents = () => ({
|
|
11
|
+
events: {},
|
|
12
|
+
emit(event, ...args) {
|
|
13
|
+
const callbacks = this.events[event] || [];
|
|
14
|
+
for (let i = 0, length = callbacks.length; i < length; i++) {
|
|
15
|
+
callbacks[i](...args);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
on(event, cb) {
|
|
19
|
+
this.events[event]?.push(cb) || (this.events[event] = [cb]);
|
|
20
|
+
return () => {
|
|
21
|
+
this.events[event] = this.events[event]?.filter((i) => cb !== i);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
var createResolver = (emitter2, method, eventId, evaluate) => {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const unbind = emitter2.on(`${method}-${eventId}`, (data) => {
|
|
28
|
+
unbind();
|
|
29
|
+
resolve(data);
|
|
30
|
+
});
|
|
31
|
+
evaluate();
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ../../shared/util/src/createRandomId.tsx
|
|
36
|
+
var TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
37
|
+
var ID_LENGTH = 21;
|
|
38
|
+
var createRandomId = (size = ID_LENGTH) => {
|
|
39
|
+
const randomValues = Array.from(
|
|
40
|
+
{ length: size },
|
|
41
|
+
() => TABLE[Math.floor(Math.random() * TABLE.length)]
|
|
42
|
+
);
|
|
43
|
+
return randomValues.join("");
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ../../shared/util/src/timeout.ts
|
|
47
|
+
var timeout = (ms) => {
|
|
48
|
+
return new Promise((_, reject) => {
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
reject(new Error("Timeout"));
|
|
51
|
+
}, ms);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/linkNativeMethod.ts
|
|
56
|
+
var emitter = createEvents();
|
|
57
|
+
var linkNativeMethod = (options = {
|
|
58
|
+
timeout: 2e3,
|
|
59
|
+
throwOnError: false
|
|
60
|
+
}) => {
|
|
61
|
+
const {
|
|
62
|
+
timeout: timeoutMs = 2e3,
|
|
63
|
+
throwOnError = false,
|
|
64
|
+
onFallback
|
|
65
|
+
} = options;
|
|
66
|
+
if (!window.ReactNativeWebView) {
|
|
67
|
+
console.warn("[WebViewBridge] Not in a WebView environment");
|
|
68
|
+
}
|
|
69
|
+
const bridgeMethods = window.__bridgeMethods__ ?? [];
|
|
70
|
+
if (!window.nativeEmitter) {
|
|
71
|
+
window.nativeEmitter = emitter;
|
|
72
|
+
}
|
|
73
|
+
const target = bridgeMethods.reduce((acc, method) => {
|
|
74
|
+
return {
|
|
75
|
+
...acc,
|
|
76
|
+
[method]: (...args) => {
|
|
77
|
+
const eventId = createRandomId();
|
|
78
|
+
return Promise.race([
|
|
79
|
+
createResolver(emitter, method, eventId, () => {
|
|
80
|
+
window.ReactNativeWebView?.postMessage(
|
|
81
|
+
JSON.stringify({
|
|
82
|
+
type: "bridge",
|
|
83
|
+
body: {
|
|
84
|
+
method,
|
|
85
|
+
eventId,
|
|
86
|
+
args
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
}),
|
|
91
|
+
timeout(timeoutMs)
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}, {});
|
|
96
|
+
return new Proxy(target, {
|
|
97
|
+
get: (target2, method) => {
|
|
98
|
+
if (method in target2) {
|
|
99
|
+
return target2[method];
|
|
100
|
+
}
|
|
101
|
+
window.ReactNativeWebView?.postMessage(
|
|
102
|
+
JSON.stringify({
|
|
103
|
+
type: "fallback",
|
|
104
|
+
body: {
|
|
105
|
+
method
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
onFallback?.(method);
|
|
110
|
+
if (throwOnError === true) {
|
|
111
|
+
return () => Promise.reject(new MethodNotFoundError(method));
|
|
112
|
+
} else if (Array.isArray(throwOnError) && throwOnError.includes(method)) {
|
|
113
|
+
return () => Promise.reject(new MethodNotFoundError(method));
|
|
114
|
+
} else {
|
|
115
|
+
console.warn(
|
|
116
|
+
`[WebViewBridge] ${method} is not defined, using fallback.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return () => Promise.resolve();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/registerWebMethod.ts
|
|
125
|
+
var registerWebMethod = (bridge) => {
|
|
126
|
+
if (!window.ReactNativeWebView) {
|
|
127
|
+
console.warn("[WebViewBridge] Not in a WebView environment");
|
|
128
|
+
return bridge;
|
|
129
|
+
}
|
|
130
|
+
const bridgeEntries = Object.entries(bridge);
|
|
131
|
+
const bridgeNames = Object.keys(bridge);
|
|
132
|
+
const emitter2 = createEvents();
|
|
133
|
+
window.webEmitter = emitter2;
|
|
134
|
+
for (const [funcName, func] of bridgeEntries) {
|
|
135
|
+
const $func = async (eventId, args) => {
|
|
136
|
+
const value = await func(...args);
|
|
137
|
+
window.ReactNativeWebView?.postMessage(
|
|
138
|
+
JSON.stringify({
|
|
139
|
+
type: "webMethodResponse",
|
|
140
|
+
body: { funcName, eventId, value }
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
emitter2.on(funcName, $func);
|
|
145
|
+
}
|
|
146
|
+
const register = () => {
|
|
147
|
+
window.ReactNativeWebView?.postMessage(
|
|
148
|
+
JSON.stringify({
|
|
149
|
+
type: "registerWebMethod",
|
|
150
|
+
body: { bridgeNames }
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
window.removeEventListener("DOMContentLoaded", register);
|
|
154
|
+
};
|
|
155
|
+
if (!window.ReactNativeWebView) {
|
|
156
|
+
window.addEventListener("DOMContentLoaded", register);
|
|
157
|
+
return bridge;
|
|
158
|
+
}
|
|
159
|
+
register();
|
|
160
|
+
return bridge;
|
|
161
|
+
};
|
|
162
|
+
export {
|
|
163
|
+
MethodNotFoundError,
|
|
164
|
+
linkNativeMethod,
|
|
165
|
+
registerWebMethod
|
|
166
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface LinkNativeMethodOptions<T extends object> {
|
|
2
|
+
timeout?: number;
|
|
3
|
+
throwOnError?: boolean | (keyof T)[];
|
|
4
|
+
onFallback?: (method: keyof T) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare const linkNativeMethod: <T extends object>(options?: LinkNativeMethodOptions<T>) => T;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type Procedure = (...args: any[]) => any;
|
|
2
|
+
export type ProceduresObject<T extends Record<string, Procedure>> = {
|
|
3
|
+
[K in keyof T]: (...args: Parameters<T[K]>) => Promise<Awaited<ReturnType<T[K]>>>;
|
|
4
|
+
};
|
|
5
|
+
export type Bridge = <T extends Record<string, Procedure>>(procedures: T) => ProceduresObject<T>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface EventsMap {
|
|
2
|
+
[event: string]: any;
|
|
3
|
+
}
|
|
4
|
+
export interface DefaultEvents extends EventsMap {
|
|
5
|
+
[event: string]: (...args: any) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface EventEmitter<Events extends EventsMap = DefaultEvents> {
|
|
8
|
+
emit<K extends keyof Events>(this: this, event: K, ...args: Parameters<Events[K]>): void;
|
|
9
|
+
events: Partial<{
|
|
10
|
+
[E in keyof Events]: Events[E][];
|
|
11
|
+
}>;
|
|
12
|
+
on<K extends keyof Events>(this: this, event: K, cb: Events[K]): () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare const createEvents: <Events extends EventsMap = DefaultEvents>() => EventEmitter<Events>;
|
|
15
|
+
export declare const createResolver: (emitter: EventEmitter<DefaultEvents>, method: string, eventId: string, evaluate: () => void) => Promise<unknown>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const createRandomId: (size?: number) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noop: () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const timeout: (ms: number) => Promise<unknown>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type Procedure = (...args: any[]) => any;
|
|
2
|
+
export type ProceduresObject<T extends Record<string, Procedure>> = {
|
|
3
|
+
[K in keyof T]: (...args: Parameters<T[K]>) => Promise<Awaited<ReturnType<T[K]>>>;
|
|
4
|
+
};
|
|
5
|
+
export type Bridge = <T extends Record<string, Procedure>>(procedures: T) => ProceduresObject<T>;
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webview-bridge/web",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Integration Web and React Native WebView",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/gronxb/webview-bridge.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"package.json"
|
|
17
|
+
],
|
|
18
|
+
"main": "/dist/commonjs/index.cjs",
|
|
19
|
+
"module": "/dist/module/index.mjs",
|
|
20
|
+
"types": "/dist/typescript/packages/web/src/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./dist/module/index.mjs",
|
|
24
|
+
"require": "./dist/commonjs/index.cjs",
|
|
25
|
+
"types": "./dist/typescript/packages/web/src/index.d.ts"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"esbuild": "^0.19.4"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "node esbuild.config.js && tsc --emitDeclarationOnly",
|
|
33
|
+
"test:type": "tsc --noEmit"
|
|
34
|
+
}
|
|
35
|
+
}
|