@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.
@@ -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.0.0",
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 ../../scripts/clean-build-output.ts dist tsconfig.tsbuildinfo && tsc -b tsconfig.json",
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.1.0"
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",