@getuserfeedback/react-native 1.0.0 → 1.3.2
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 +148 -1
- package/dist/host-viewport.d.ts +3 -0
- package/dist/host-viewport.js +69 -0
- package/dist/index.d.ts +80 -14
- package/dist/index.js +894 -10
- package/dist/version.d.ts +1 -0
- package/dist/version.js +6 -0
- package/dist/webview-bridge-script.d.ts +16 -0
- package/dist/webview-bridge-script.js +290 -0
- package/dist/widget-host-lifecycle-machine.d.ts +34 -0
- package/dist/widget-host-lifecycle-machine.js +37 -0
- package/dist/widget-host.d.ts +38 -0
- package/dist/widget-host.js +327 -0
- package/package.json +8 -6
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { parseWebViewTransportNativeMessage, parseWebViewTransportWebMessage, } from "@getuserfeedback/protocol/webview-transport";
|
|
2
|
+
import { createElement, useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
3
|
+
import { buildHostViewportInjectionScript, buildHostViewportResetInjectionScript, buildLoaderHtml, buildNativeMessageInjectionScript, buildWebViewBridgeScript, } from "./webview-bridge-script.js";
|
|
4
|
+
import { createWidgetHostLifecycleController, } from "./widget-host-lifecycle-machine.js";
|
|
5
|
+
const EMPTY_HTML_SOURCE = {
|
|
6
|
+
html: '<!doctype html><html><head><meta charset="utf-8"></head><body></body></html>',
|
|
7
|
+
};
|
|
8
|
+
function resolveWebViewComponent() {
|
|
9
|
+
var _a;
|
|
10
|
+
const webViewModule = require("react-native-webview");
|
|
11
|
+
const WebView = (_a = webViewModule.WebView) !== null && _a !== void 0 ? _a : webViewModule.default;
|
|
12
|
+
if (!WebView) {
|
|
13
|
+
throw new Error("react-native-webview must export WebView to render WidgetHost");
|
|
14
|
+
}
|
|
15
|
+
return WebView;
|
|
16
|
+
}
|
|
17
|
+
function toMessageData(data) {
|
|
18
|
+
if (typeof data !== "string") {
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(data);
|
|
23
|
+
}
|
|
24
|
+
catch (_a) {
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function toSourceKey(source) {
|
|
29
|
+
return JSON.stringify(source);
|
|
30
|
+
}
|
|
31
|
+
function toWidgetHostSourceKey(input) {
|
|
32
|
+
if (input.source) {
|
|
33
|
+
return toSourceKey(input.source);
|
|
34
|
+
}
|
|
35
|
+
if (input.loaderUrl) {
|
|
36
|
+
return JSON.stringify({
|
|
37
|
+
bridgeInjectionScript: input.bridgeInjectionScript,
|
|
38
|
+
loaderUrl: input.loaderUrl,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return toSourceKey(EMPTY_HTML_SOURCE);
|
|
42
|
+
}
|
|
43
|
+
function toResolvedSource(input) {
|
|
44
|
+
if (input.source) {
|
|
45
|
+
return input.source;
|
|
46
|
+
}
|
|
47
|
+
if (!input.loaderUrl) {
|
|
48
|
+
return EMPTY_HTML_SOURCE;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
html: buildLoaderHtml(input.loaderUrl, {
|
|
52
|
+
bridgeScript: composeBridgeInjectionScripts([
|
|
53
|
+
input.hostViewportBootstrapScript,
|
|
54
|
+
input.bridgeInjectionScript,
|
|
55
|
+
]),
|
|
56
|
+
}),
|
|
57
|
+
baseUrl: input.loaderUrl,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function isHtmlSource(source) {
|
|
61
|
+
return "html" in source;
|
|
62
|
+
}
|
|
63
|
+
function toInjectedJavaScript(value) {
|
|
64
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
65
|
+
}
|
|
66
|
+
function composeInjectedJavaScript(input) {
|
|
67
|
+
const callerInjectionScript = toInjectedJavaScript(input.callerInjectionScript);
|
|
68
|
+
if (!callerInjectionScript) {
|
|
69
|
+
return input.bridgeInjectionScript;
|
|
70
|
+
}
|
|
71
|
+
return `${callerInjectionScript}\n${input.bridgeInjectionScript}`;
|
|
72
|
+
}
|
|
73
|
+
function toWebViewMessageHandler(value) {
|
|
74
|
+
return typeof value === "function"
|
|
75
|
+
? value
|
|
76
|
+
: undefined;
|
|
77
|
+
}
|
|
78
|
+
function composeBridgeInjectionScripts(scripts) {
|
|
79
|
+
return scripts
|
|
80
|
+
.filter((script) => Boolean(script))
|
|
81
|
+
.join("\n");
|
|
82
|
+
}
|
|
83
|
+
function isValidHostViewport(hostViewport) {
|
|
84
|
+
return (typeof (hostViewport === null || hostViewport === void 0 ? void 0 : hostViewport.width) === "number" &&
|
|
85
|
+
Number.isFinite(hostViewport.width) &&
|
|
86
|
+
hostViewport.width > 0 &&
|
|
87
|
+
typeof hostViewport.height === "number" &&
|
|
88
|
+
Number.isFinite(hostViewport.height) &&
|
|
89
|
+
hostViewport.height > 0);
|
|
90
|
+
}
|
|
91
|
+
export function WidgetHost({ hostViewport, instanceId, loaderUrl, nativeMessages, onInvalidWebMessage, onReady, onReadyStateChange, onWebMessage, source, webViewComponent, webViewProps, }) {
|
|
92
|
+
var _a, _b, _c, _d;
|
|
93
|
+
const [, setLifecycleRevision] = useState(0);
|
|
94
|
+
const WebView = webViewComponent !== null && webViewComponent !== void 0 ? webViewComponent : resolveWebViewComponent();
|
|
95
|
+
const bridgeInjectionScript = useMemo(() => buildWebViewBridgeScript({ instanceId }), [instanceId]);
|
|
96
|
+
const hasValidHostViewport = isValidHostViewport(hostViewport);
|
|
97
|
+
const hostViewportWidth = hasValidHostViewport
|
|
98
|
+
? hostViewport.width
|
|
99
|
+
: undefined;
|
|
100
|
+
const hostViewportHeight = hasValidHostViewport
|
|
101
|
+
? hostViewport.height
|
|
102
|
+
: undefined;
|
|
103
|
+
const hostViewportSignature = hostViewportWidth === undefined || hostViewportHeight === undefined
|
|
104
|
+
? null
|
|
105
|
+
: `${hostViewportWidth}:${hostViewportHeight}`;
|
|
106
|
+
const hostViewportVersionRef = useRef({ signature: null, version: 0 });
|
|
107
|
+
if (hostViewportVersionRef.current.signature !== hostViewportSignature) {
|
|
108
|
+
hostViewportVersionRef.current = {
|
|
109
|
+
signature: hostViewportSignature,
|
|
110
|
+
version: hostViewportVersionRef.current.version + 1,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const hostViewportVersion = hostViewportVersionRef.current.version;
|
|
114
|
+
const hostViewportInjectionScript = useMemo(() => hostViewportWidth === undefined || hostViewportHeight === undefined
|
|
115
|
+
? undefined
|
|
116
|
+
: buildHostViewportInjectionScript({
|
|
117
|
+
width: hostViewportWidth,
|
|
118
|
+
height: hostViewportHeight,
|
|
119
|
+
version: hostViewportVersion,
|
|
120
|
+
}), [hostViewportHeight, hostViewportVersion, hostViewportWidth]);
|
|
121
|
+
const sourceKey = useMemo(() => toWidgetHostSourceKey({ bridgeInjectionScript, loaderUrl, source }), [bridgeInjectionScript, loaderUrl, source]);
|
|
122
|
+
const lifecycleControllerRef = useRef(null);
|
|
123
|
+
if (!lifecycleControllerRef.current ||
|
|
124
|
+
lifecycleControllerRef.current.getSnapshot().sourceKey !== sourceKey) {
|
|
125
|
+
lifecycleControllerRef.current = createWidgetHostLifecycleController({
|
|
126
|
+
onChange: () => {
|
|
127
|
+
setLifecycleRevision((revision) => revision + 1);
|
|
128
|
+
},
|
|
129
|
+
sourceKey,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const lifecycleController = lifecycleControllerRef.current;
|
|
133
|
+
const lifecycleSnapshot = lifecycleController.getSnapshot();
|
|
134
|
+
const resolvedSourceRef = useRef(null);
|
|
135
|
+
const sourceBootstrapSignature = hostViewportInjectionScript
|
|
136
|
+
? hostViewportSignature
|
|
137
|
+
: null;
|
|
138
|
+
const hasBootstrapSignatureChanged = ((_a = resolvedSourceRef.current) === null || _a === void 0 ? void 0 : _a.bootstrapSignature) !== sourceBootstrapSignature;
|
|
139
|
+
const canRefreshBootstrapSource = lifecycleSnapshot.handle === null || sourceBootstrapSignature === null;
|
|
140
|
+
const shouldRefreshResolvedSource = !resolvedSourceRef.current ||
|
|
141
|
+
resolvedSourceRef.current.sourceKey !== sourceKey ||
|
|
142
|
+
(hasBootstrapSignatureChanged && canRefreshBootstrapSource);
|
|
143
|
+
if (shouldRefreshResolvedSource) {
|
|
144
|
+
resolvedSourceRef.current = {
|
|
145
|
+
bootstrapSignature: sourceBootstrapSignature,
|
|
146
|
+
source: toResolvedSource({
|
|
147
|
+
bridgeInjectionScript,
|
|
148
|
+
hostViewportBootstrapScript: hostViewportInjectionScript,
|
|
149
|
+
loaderUrl,
|
|
150
|
+
source,
|
|
151
|
+
}),
|
|
152
|
+
sourceKey,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const resolvedSource = (_c = (_b = resolvedSourceRef.current) === null || _b === void 0 ? void 0 : _b.source) !== null && _c !== void 0 ? _c : EMPTY_HTML_SOURCE;
|
|
156
|
+
const usesHtmlSource = isHtmlSource(resolvedSource);
|
|
157
|
+
const isDeliverable = lifecycleSnapshot.state === "ready" &&
|
|
158
|
+
lifecycleSnapshot.sourceKey === sourceKey &&
|
|
159
|
+
typeof ((_d = lifecycleSnapshot.handle) === null || _d === void 0 ? void 0 : _d.injectJavaScript) === "function";
|
|
160
|
+
const readyStateNotificationKey = `${sourceKey}:${lifecycleSnapshot.readySequence}:${isDeliverable}`;
|
|
161
|
+
const readyStateNotificationKeyRef = useRef(null);
|
|
162
|
+
const deferNativeMessageInjectionRef = useRef(false);
|
|
163
|
+
const renderedHostViewportSourceKeyRef = useRef(null);
|
|
164
|
+
const injectedHostViewportRef = useRef(null);
|
|
165
|
+
if (hostViewportInjectionScript) {
|
|
166
|
+
renderedHostViewportSourceKeyRef.current = sourceKey;
|
|
167
|
+
}
|
|
168
|
+
const callerOnMessage = toWebViewMessageHandler(webViewProps === null || webViewProps === void 0 ? void 0 : webViewProps.onMessage);
|
|
169
|
+
const injectedJavaScript = useMemo(() => composeInjectedJavaScript({
|
|
170
|
+
bridgeInjectionScript: composeBridgeInjectionScripts([
|
|
171
|
+
hostViewportInjectionScript,
|
|
172
|
+
bridgeInjectionScript,
|
|
173
|
+
]),
|
|
174
|
+
callerInjectionScript: webViewProps === null || webViewProps === void 0 ? void 0 : webViewProps.injectedJavaScript,
|
|
175
|
+
}), [
|
|
176
|
+
bridgeInjectionScript,
|
|
177
|
+
hostViewportInjectionScript,
|
|
178
|
+
webViewProps === null || webViewProps === void 0 ? void 0 : webViewProps.injectedJavaScript,
|
|
179
|
+
]);
|
|
180
|
+
const injectedJavaScriptBeforeContentLoaded = useMemo(() => composeInjectedJavaScript({
|
|
181
|
+
bridgeInjectionScript: composeBridgeInjectionScripts([
|
|
182
|
+
hostViewportInjectionScript,
|
|
183
|
+
bridgeInjectionScript,
|
|
184
|
+
]),
|
|
185
|
+
callerInjectionScript: webViewProps === null || webViewProps === void 0 ? void 0 : webViewProps.injectedJavaScriptBeforeContentLoaded,
|
|
186
|
+
}), [
|
|
187
|
+
bridgeInjectionScript,
|
|
188
|
+
hostViewportInjectionScript,
|
|
189
|
+
webViewProps === null || webViewProps === void 0 ? void 0 : webViewProps.injectedJavaScriptBeforeContentLoaded,
|
|
190
|
+
]);
|
|
191
|
+
const setWebViewRef = useCallback((handle) => {
|
|
192
|
+
if (!handle || typeof handle.injectJavaScript !== "function") {
|
|
193
|
+
lifecycleController.send({ type: "refDetached" });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
lifecycleController.send({ type: "refAttached", handle });
|
|
197
|
+
}, [lifecycleController]);
|
|
198
|
+
const handleMessage = useCallback((event) => {
|
|
199
|
+
var _a;
|
|
200
|
+
const rawMessage = toMessageData((_a = event.nativeEvent) === null || _a === void 0 ? void 0 : _a.data);
|
|
201
|
+
const message = (() => {
|
|
202
|
+
try {
|
|
203
|
+
return parseWebViewTransportWebMessage(rawMessage);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
if (!callerOnMessage) {
|
|
207
|
+
onInvalidWebMessage === null || onInvalidWebMessage === void 0 ? void 0 : onInvalidWebMessage(error instanceof Error ? error : new Error(String(error)), rawMessage);
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
})();
|
|
212
|
+
if (!message) {
|
|
213
|
+
callerOnMessage === null || callerOnMessage === void 0 ? void 0 : callerOnMessage(event);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (message.kind === "ready") {
|
|
217
|
+
lifecycleController.send({ type: "bridgeReady", sourceKey });
|
|
218
|
+
}
|
|
219
|
+
onWebMessage === null || onWebMessage === void 0 ? void 0 : onWebMessage(message);
|
|
220
|
+
callerOnMessage === null || callerOnMessage === void 0 ? void 0 : callerOnMessage(event);
|
|
221
|
+
}, [
|
|
222
|
+
callerOnMessage,
|
|
223
|
+
lifecycleController,
|
|
224
|
+
onInvalidWebMessage,
|
|
225
|
+
onWebMessage,
|
|
226
|
+
sourceKey,
|
|
227
|
+
]);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (readyStateNotificationKeyRef.current === readyStateNotificationKey) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
readyStateNotificationKeyRef.current = readyStateNotificationKey;
|
|
233
|
+
const event = {
|
|
234
|
+
isReady: isDeliverable,
|
|
235
|
+
readySequence: lifecycleSnapshot.readySequence,
|
|
236
|
+
sourceKey,
|
|
237
|
+
};
|
|
238
|
+
const readyStateChangeResult = onReadyStateChange === null || onReadyStateChange === void 0 ? void 0 : onReadyStateChange(event);
|
|
239
|
+
deferNativeMessageInjectionRef.current = Boolean(event.isReady &&
|
|
240
|
+
readyStateChangeResult &&
|
|
241
|
+
typeof readyStateChangeResult === "object" &&
|
|
242
|
+
"deferNativeMessageInjection" in readyStateChangeResult &&
|
|
243
|
+
readyStateChangeResult.deferNativeMessageInjection);
|
|
244
|
+
if (event.isReady) {
|
|
245
|
+
onReady === null || onReady === void 0 ? void 0 : onReady({
|
|
246
|
+
readySequence: event.readySequence,
|
|
247
|
+
sourceKey: event.sourceKey,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}, [
|
|
251
|
+
isDeliverable,
|
|
252
|
+
lifecycleSnapshot.readySequence,
|
|
253
|
+
onReady,
|
|
254
|
+
onReadyStateChange,
|
|
255
|
+
readyStateNotificationKey,
|
|
256
|
+
sourceKey,
|
|
257
|
+
]);
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
var _a, _b;
|
|
260
|
+
const injectJavaScript = (_a = lifecycleSnapshot.handle) === null || _a === void 0 ? void 0 : _a.injectJavaScript;
|
|
261
|
+
if (typeof injectJavaScript !== "function") {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (hostViewportInjectionScript) {
|
|
265
|
+
injectJavaScript(hostViewportInjectionScript);
|
|
266
|
+
injectedHostViewportRef.current = {
|
|
267
|
+
sourceKey,
|
|
268
|
+
};
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (((_b = injectedHostViewportRef.current) === null || _b === void 0 ? void 0 : _b.sourceKey) !== sourceKey &&
|
|
272
|
+
renderedHostViewportSourceKeyRef.current !== sourceKey) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
injectJavaScript(buildHostViewportResetInjectionScript({
|
|
276
|
+
version: hostViewportVersion,
|
|
277
|
+
}));
|
|
278
|
+
injectedHostViewportRef.current = null;
|
|
279
|
+
renderedHostViewportSourceKeyRef.current = null;
|
|
280
|
+
}, [
|
|
281
|
+
hostViewportInjectionScript,
|
|
282
|
+
hostViewportVersion,
|
|
283
|
+
lifecycleSnapshot.handle,
|
|
284
|
+
sourceKey,
|
|
285
|
+
]);
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
var _a;
|
|
288
|
+
if (!nativeMessages || nativeMessages.length === 0) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (lifecycleSnapshot.state !== "ready" ||
|
|
292
|
+
lifecycleSnapshot.sourceKey !== sourceKey) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (deferNativeMessageInjectionRef.current) {
|
|
296
|
+
deferNativeMessageInjectionRef.current = false;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const injectJavaScript = (_a = lifecycleSnapshot.handle) === null || _a === void 0 ? void 0 : _a.injectJavaScript;
|
|
300
|
+
if (typeof injectJavaScript !== "function") {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const deliveredMessageIds = new Set(lifecycleSnapshot.deliveredMessageIds);
|
|
304
|
+
for (const nativeMessage of nativeMessages) {
|
|
305
|
+
const message = parseWebViewTransportNativeMessage(nativeMessage);
|
|
306
|
+
if (deliveredMessageIds.has(message.id)) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
injectJavaScript(buildNativeMessageInjectionScript(message));
|
|
310
|
+
deliveredMessageIds.add(message.id);
|
|
311
|
+
lifecycleController.send({
|
|
312
|
+
type: "commandInjected",
|
|
313
|
+
messageId: message.id,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}, [
|
|
317
|
+
lifecycleController,
|
|
318
|
+
lifecycleSnapshot.deliveredMessageIds,
|
|
319
|
+
lifecycleSnapshot.handle,
|
|
320
|
+
lifecycleSnapshot.sourceKey,
|
|
321
|
+
lifecycleSnapshot.state,
|
|
322
|
+
nativeMessages,
|
|
323
|
+
sourceKey,
|
|
324
|
+
]);
|
|
325
|
+
return createElement(WebView, Object.assign(Object.assign(Object.assign({}, (usesHtmlSource ? { originWhitelist: ["*"] } : {})), webViewProps), { injectedJavaScript,
|
|
326
|
+
injectedJavaScriptBeforeContentLoaded, key: sourceKey, onMessage: handleMessage, ref: setWebViewRef, source: resolvedSource }));
|
|
327
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getuserfeedback/react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "getuserfeedback React Native SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"getuserfeedback",
|
|
@@ -34,16 +34,18 @@
|
|
|
34
34
|
"scripts": {
|
|
35
35
|
"prepack": "node scripts/prepack.cjs",
|
|
36
36
|
"postpack": "node scripts/postpack.cjs",
|
|
37
|
-
"pack:verify": "node ../../scripts/pack-and-verify.cjs --expect-dependency @getuserfeedback/protocol:../protocol/package.json",
|
|
38
|
-
"publish:dry-run": "node ../../scripts/publish-package.cjs . --expect-dependency @getuserfeedback/protocol:../protocol/package.json -- --dry-run",
|
|
39
|
-
"publish:npm": "node ../../scripts/publish-package.cjs . --expect-dependency @getuserfeedback/protocol:../protocol/package.json",
|
|
40
|
-
"build": "bun
|
|
37
|
+
"pack:verify": "node ../../scripts/pack-and-verify.cjs --expect-dependency @getuserfeedback/protocol:../protocol/package.json --expect-dependency @getuserfeedback/sdk:../sdk/package.json",
|
|
38
|
+
"publish:dry-run": "node ../../scripts/publish-package.cjs . --expect-dependency @getuserfeedback/protocol:../protocol/package.json --expect-dependency @getuserfeedback/sdk:../sdk/package.json -- --dry-run",
|
|
39
|
+
"publish:npm": "node ../../scripts/publish-package.cjs . --expect-dependency @getuserfeedback/protocol:../protocol/package.json --expect-dependency @getuserfeedback/sdk:../sdk/package.json",
|
|
40
|
+
"build": "bun scripts/build.ts",
|
|
41
41
|
"typecheck": "tsc -b tsconfig.json",
|
|
42
42
|
"test": "bun test --dots",
|
|
43
43
|
"lint": "ultracite check ."
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@getuserfeedback/protocol": "^1.
|
|
46
|
+
"@getuserfeedback/protocol": "^1.2.0",
|
|
47
|
+
"@getuserfeedback/sdk": "^0.7.0",
|
|
48
|
+
"robot3": "^1.2.0"
|
|
47
49
|
},
|
|
48
50
|
"peerDependencies": {
|
|
49
51
|
"react": ">=18.0.0",
|