@getuserfeedback/react-native 3.0.32 → 3.0.33

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/dist/version.js CHANGED
@@ -3,4 +3,4 @@ const reactNativeSdkVersion = typeof __GX_REACT_NATIVE_SDK_VERSION__ === "string
3
3
  : "";
4
4
  // Build scripts patch this fallback to the package version for published artifacts.
5
5
  // Source-linked workspace usage keeps the local fallback.
6
- export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "3.0.32";
6
+ export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "3.0.33";
@@ -0,0 +1,25 @@
1
+ import { type WebViewConnectionBridgeMessage } from "./webview-connection-bridge-message.js";
2
+ export type ViewWebViewBridgeMessage = WebViewConnectionBridgeMessage;
3
+ export declare const parseViewWebViewBridgeMessage: (value: unknown) => ViewWebViewBridgeMessage;
4
+ export declare const buildViewWebViewBridgeScript: (input: {
5
+ bridgeId: string;
6
+ phase: "documentStart";
7
+ } | {
8
+ bridgeId: string;
9
+ epoch: number;
10
+ phase: "afterLoad";
11
+ }) => string;
12
+ export declare const toExternalViewSource: (input: {
13
+ bridgeId: string;
14
+ srcdoc: string;
15
+ }) => {
16
+ html: string;
17
+ baseUrl: string;
18
+ };
19
+ export declare const buildViewWebViewHostMessageScript: (input: {
20
+ bridgeId: string;
21
+ connectionId: string;
22
+ epoch: number;
23
+ generation: number;
24
+ message: unknown;
25
+ }) => string;
@@ -0,0 +1,124 @@
1
+ import { VIEW_HOST_BOOTSTRAP_GLOBAL_KEY, VIEW_HOST_BOOTSTRAP_PROTOCOL_VERSION, VIEW_HOST_BOOTSTRAP_READY_EVENT_NAME, VIEW_HOST_BOOTSTRAP_REQUIRED_FRAGMENT, } from "@getuserfeedback/protocol/internal/view-host-bootstrap";
2
+ import { parseWebViewConnectionBridgeMessage, } from "./webview-connection-bridge-message.js";
3
+ import { buildWebViewConnectionBridgeScript, } from "./webview-connection-bridge-script.js";
4
+ import { buildWebViewConnectionHostMessageScript, serializeForJavaScript, } from "./webview-connection-host-message-script.js";
5
+ const VIEW_WEBVIEW_BRIDGE_GLOBAL_KEY = "__getuserfeedback_view_webview_bridge";
6
+ const VIEW_WEBVIEW_BRIDGE_PROTOCOL_VERSION = "v1";
7
+ const VIEW_WEBVIEW_BRIDGE_CONFIG = {
8
+ bridgeGlobalKey: VIEW_WEBVIEW_BRIDGE_GLOBAL_KEY,
9
+ bridgeProtocolVersion: VIEW_WEBVIEW_BRIDGE_PROTOCOL_VERSION,
10
+ connectTimeoutMessage: "Timed out connecting view to the native host",
11
+ connectTimeoutMs: 30000,
12
+ inactiveConnectionMessage: "View WebView bridge connection is not active",
13
+ };
14
+ export const parseViewWebViewBridgeMessage = (value) => parseWebViewConnectionBridgeMessage(value, {
15
+ invalidIdentity: "Invalid view WebView bridge message identity",
16
+ invalidKind: "Invalid view WebView bridge message kind",
17
+ invalidMessage: "Invalid view WebView bridge message",
18
+ missingPayload: "View WebView bridge payload is missing",
19
+ });
20
+ const buildViewHostBootstrapScript = () => {
21
+ const bridgeGlobalKey = serializeForJavaScript(VIEW_WEBVIEW_BRIDGE_GLOBAL_KEY);
22
+ const bootstrapGlobalKey = serializeForJavaScript(VIEW_HOST_BOOTSTRAP_GLOBAL_KEY);
23
+ const bootstrapProtocolVersion = serializeForJavaScript(VIEW_HOST_BOOTSTRAP_PROTOCOL_VERSION);
24
+ const bootstrapReadyEventName = serializeForJavaScript(VIEW_HOST_BOOTSTRAP_READY_EVENT_NAME);
25
+ const inactiveConnectionMessage = serializeForJavaScript(VIEW_WEBVIEW_BRIDGE_CONFIG.inactiveConnectionMessage);
26
+ return `
27
+ (function () {
28
+ var bridge = window[${bridgeGlobalKey}];
29
+ window[${bootstrapGlobalKey}] = {
30
+ protocolVersion: ${bootstrapProtocolVersion},
31
+ createViewHostConnection: function (options) {
32
+ var config = options.config;
33
+ var transport = null;
34
+ var connection = bridge.createConnection({
35
+ connectionIdentity: { viewId: config.viewId },
36
+ generation: config.generation,
37
+ onActivated: function (activeTransport) {
38
+ transport = activeTransport;
39
+ if (typeof options.onMessage === "function") {
40
+ activeTransport.subscribe(options.onMessage);
41
+ }
42
+ activeTransport.postMessage({
43
+ t: "getuserfeedback:action:viewConnect",
44
+ gen: config.generation,
45
+ viewId: config.viewId
46
+ });
47
+ },
48
+ onError: options.onError
49
+ });
50
+
51
+ function post(message) {
52
+ return connection.connect().then(function () {
53
+ if (!transport) {
54
+ throw new Error(${inactiveConnectionMessage});
55
+ }
56
+ transport.postMessage(message);
57
+ });
58
+ }
59
+
60
+ return {
61
+ connect: connection.connect,
62
+ disconnect: connection.disconnect,
63
+ requestClose: function () {
64
+ return post({
65
+ t: "getuserfeedback:action:requestCloseView",
66
+ gen: config.generation,
67
+ viewId: config.viewId
68
+ });
69
+ },
70
+ postViewSize: function (measurement) {
71
+ return post({
72
+ t: "getuserfeedback:event:viewSize",
73
+ gen: config.generation,
74
+ viewId: config.viewId,
75
+ width: measurement.width,
76
+ height: measurement.height,
77
+ intrinsicWidth: measurement.intrinsicWidth,
78
+ frameAppearance: measurement.frameAppearance,
79
+ placement: measurement.placement,
80
+ transitionPolicy: measurement.transitionPolicy,
81
+ layoutTransactionId: measurement.layoutTransactionId
82
+ });
83
+ },
84
+ post: post
85
+ };
86
+ }
87
+ };
88
+ window.dispatchEvent(new Event(${bootstrapReadyEventName}));
89
+ })();
90
+ true;`;
91
+ };
92
+ export const buildViewWebViewBridgeScript = (input) => `${buildWebViewConnectionBridgeScript(Object.assign(Object.assign({}, input), { config: VIEW_WEBVIEW_BRIDGE_CONFIG }))}\n${buildViewHostBootstrapScript()}`;
93
+ const escapeInlineScript = (value) => value.replace(/<\/script/gi, "<\\/script");
94
+ export const toExternalViewSource = (input) => {
95
+ const headStart = input.srcdoc.match(/<head(?:\s[^>]*)?>/i);
96
+ if (!headStart || headStart.index === undefined) {
97
+ throw new Error("View srcdoc must include a head element");
98
+ }
99
+ const insertionIndex = headStart.index + headStart[0].length;
100
+ const bridgeScript = buildViewWebViewBridgeScript({
101
+ bridgeId: input.bridgeId,
102
+ phase: "documentStart",
103
+ });
104
+ const requiredFragment = serializeForJavaScript(VIEW_HOST_BOOTSTRAP_REQUIRED_FRAGMENT);
105
+ const selectExternalHost = `
106
+ (function () {
107
+ var fragment = ${requiredFragment};
108
+ try {
109
+ history.replaceState(null, "", "#" + fragment);
110
+ } catch {
111
+ location.hash = fragment;
112
+ }
113
+ })();
114
+ true;`;
115
+ // The inline bootstrap guarantees it precedes the bundled view runtime.
116
+ // WebViewDocumentHost also installs the same idempotent script through the
117
+ // native document-start hook because that hook's timing varies by platform.
118
+ const bootstrap = `<script>${escapeInlineScript(`${selectExternalHost}\n${bridgeScript}`)}</script>`;
119
+ return {
120
+ html: `${input.srcdoc.slice(0, insertionIndex)}${bootstrap}${input.srcdoc.slice(insertionIndex)}`,
121
+ baseUrl: `https://view.getuserfeedback.invalid/#${VIEW_HOST_BOOTSTRAP_REQUIRED_FRAGMENT}`,
122
+ };
123
+ };
124
+ export const buildViewWebViewHostMessageScript = (input) => buildWebViewConnectionHostMessageScript(Object.assign(Object.assign({}, input), { bridgeGlobalKey: VIEW_WEBVIEW_BRIDGE_GLOBAL_KEY }));
@@ -0,0 +1,19 @@
1
+ import { type ViewWebViewBridgeMessage } from "./view-webview-bridge-adapter.js";
2
+ import { type WebViewHostController } from "./webview-host-controller.js";
3
+ export type ViewWebViewHostConnection = {
4
+ generation: number;
5
+ transport: {
6
+ postMessage: (message: unknown) => void;
7
+ subscribe: (listener: (message: unknown) => void) => () => void;
8
+ };
9
+ };
10
+ export type ViewWebViewHostController = WebViewHostController<ViewWebViewBridgeMessage>;
11
+ export declare const createViewWebViewHostController: (input: {
12
+ bridgeId: string;
13
+ viewId: string;
14
+ generation: number;
15
+ onConnected: (connection: ViewWebViewHostConnection) => void;
16
+ onDisconnected?: () => void;
17
+ activationRetryIntervalMs?: number;
18
+ activationRetryTimeoutMs?: number;
19
+ }) => ViewWebViewHostController;
@@ -0,0 +1,33 @@
1
+ import { buildViewWebViewBridgeScript, buildViewWebViewHostMessageScript, } from "./view-webview-bridge-adapter.js";
2
+ import { createWebViewHostController, } from "./webview-host-controller.js";
3
+ const hasViewIdentity = (value, viewId) => typeof value === "object" &&
4
+ value !== null &&
5
+ "viewId" in value &&
6
+ value.viewId === viewId;
7
+ export const createViewWebViewHostController = (input) => {
8
+ const controller = createWebViewHostController({
9
+ bridgeId: input.bridgeId,
10
+ connectionLabel: "View WebView host",
11
+ buildActivationScript: buildViewWebViewBridgeScript,
12
+ buildHostMessageScript: buildViewWebViewHostMessageScript,
13
+ onConnected: (connection) => {
14
+ input.onConnected({
15
+ generation: connection.generation,
16
+ transport: {
17
+ postMessage: connection.postMessage,
18
+ subscribe: (listener) => connection.subscribe((message) => {
19
+ if (message.kind === "message") {
20
+ listener(message.message);
21
+ }
22
+ }),
23
+ },
24
+ });
25
+ },
26
+ onDisconnected: input.onDisconnected,
27
+ activationRetryIntervalMs: input.activationRetryIntervalMs,
28
+ activationRetryTimeoutMs: input.activationRetryTimeoutMs,
29
+ });
30
+ return Object.assign(Object.assign({}, controller), { handleMessage: (message) => message.generation === input.generation &&
31
+ hasViewIdentity(message.connectionIdentity, input.viewId) &&
32
+ controller.handleMessage(message) });
33
+ };
@@ -0,0 +1,13 @@
1
+ import { type ComponentType, type ReactElement } from "react";
2
+ import { type ViewWebViewHostConnection } from "./view-webview-host-controller.js";
3
+ export type ViewWebViewHostProps = {
4
+ viewId: string;
5
+ generation: number;
6
+ srcdoc: string;
7
+ onConnected: (connection: ViewWebViewHostConnection) => void;
8
+ onDisconnected?: () => void;
9
+ onInvalidMessage?: (error: Error, value: unknown) => void;
10
+ webViewComponent?: ComponentType<Record<string, unknown>>;
11
+ webViewProps?: Record<string, unknown>;
12
+ };
13
+ export declare function ViewWebViewHost(props: ViewWebViewHostProps): ReactElement;
@@ -0,0 +1,54 @@
1
+ import { createElement, useMemo, useRef, } from "react";
2
+ import { buildViewWebViewBridgeScript, parseViewWebViewBridgeMessage, toExternalViewSource, } from "./view-webview-bridge-adapter.js";
3
+ import { createViewWebViewHostController, } from "./view-webview-host-controller.js";
4
+ import { WebViewDocumentHost } from "./webview-document-host.js";
5
+ let nextBridgeId = 0;
6
+ function ViewWebViewHostDocument({ generation, onConnected, onDisconnected, onInvalidMessage, srcdoc, viewId, webViewComponent, webViewProps, }) {
7
+ const bridgeIdRef = useRef(null);
8
+ if (bridgeIdRef.current === null) {
9
+ nextBridgeId += 1;
10
+ bridgeIdRef.current = `view-webview-${nextBridgeId}`;
11
+ }
12
+ const bridgeId = bridgeIdRef.current;
13
+ const onConnectedRef = useRef(onConnected);
14
+ const onDisconnectedRef = useRef(onDisconnected);
15
+ onConnectedRef.current = onConnected;
16
+ onDisconnectedRef.current = onDisconnected;
17
+ const controllerRef = useRef(null);
18
+ if (controllerRef.current === null) {
19
+ controllerRef.current = createViewWebViewHostController({
20
+ bridgeId,
21
+ generation,
22
+ viewId,
23
+ onConnected: (connection) => {
24
+ onConnectedRef.current(connection);
25
+ },
26
+ onDisconnected: () => {
27
+ var _a;
28
+ (_a = onDisconnectedRef.current) === null || _a === void 0 ? void 0 : _a.call(onDisconnectedRef);
29
+ },
30
+ });
31
+ }
32
+ const controller = controllerRef.current;
33
+ const buildDocumentStartScript = useMemo(() => () => buildViewWebViewBridgeScript({ bridgeId, phase: "documentStart" }), [bridgeId]);
34
+ const source = useMemo(() => toExternalViewSource({ bridgeId, srcdoc }), [bridgeId, srcdoc]);
35
+ const resolvedWebViewProps = useMemo(() => {
36
+ var _a;
37
+ return (Object.assign(Object.assign({}, webViewProps), { originWhitelist: (_a = webViewProps === null || webViewProps === void 0 ? void 0 : webViewProps.originWhitelist) !== null && _a !== void 0 ? _a : ["*"] }));
38
+ }, [webViewProps]);
39
+ return createElement((WebViewDocumentHost), {
40
+ bridgeId,
41
+ buildDocumentStartScript,
42
+ controller,
43
+ parseMessage: parseViewWebViewBridgeMessage,
44
+ source,
45
+ webViewComponent,
46
+ webViewProps: resolvedWebViewProps,
47
+ onInvalidMessage,
48
+ });
49
+ }
50
+ export function ViewWebViewHost(props) {
51
+ return createElement(ViewWebViewHostDocument, Object.assign(Object.assign({}, props), { key: JSON.stringify([props.viewId, props.generation, props.srcdoc]) }));
52
+ }
53
+ // TODO(migration): Wire core-native view allocation and message routing through the preparation registry into this private per-view host. Keep the provider runtime active while proving one native-owned view end to end.
54
+ // [from=private-native-per-view-webview-adapter] [to=private-native-view-allocation-routing] [scope=slice] [priority=high] [impact=high] [risk=high] [epic=runtime-neutral-view-host-orchestration] [epic_order=20]
@@ -3,6 +3,7 @@ type WebViewConnectionBridgeMessageIdentity = {
3
3
  connectionId: string;
4
4
  epoch: number;
5
5
  generation: number;
6
+ connectionIdentity?: unknown;
6
7
  };
7
8
  export type WebViewConnectionBridgeMessage = (WebViewConnectionBridgeMessageIdentity & {
8
9
  kind: "connected";
@@ -38,6 +38,7 @@ export const buildWebViewConnectionBridgeScript = (input) => {
38
38
  ? crypto.randomUUID()
39
39
  : String(Date.now()) + ":" + String(Math.random());
40
40
  var activeConnection = null;
41
+ var isClosed = false;
41
42
  var isNativeActivated = ${String(activateNative)};
42
43
  var nativeEpoch = ${String(activationEpoch)};
43
44
 
@@ -58,6 +59,9 @@ export const buildWebViewConnectionBridgeScript = (input) => {
58
59
  generation: connection.generation,
59
60
  kind: kind
60
61
  };
62
+ if (connection.connectionIdentity !== undefined) {
63
+ envelope.connectionIdentity = connection.connectionIdentity;
64
+ }
61
65
  if (kind === "message") {
62
66
  envelope.message = message;
63
67
  }
@@ -79,8 +83,11 @@ export const buildWebViewConnectionBridgeScript = (input) => {
79
83
  connectionId:
80
84
  bridgeId + ":" + documentId + ":" + String(++connectionSequence),
81
85
  generation: options.generation,
86
+ connectionIdentity: options.connectionIdentity,
82
87
  listener: null,
83
88
  isConnected: false,
89
+ isDisconnected: false,
90
+ connectPromise: null,
84
91
  connectTimeout: null,
85
92
  onActivated: options.onActivated,
86
93
  onError: options.onError,
@@ -91,6 +98,9 @@ export const buildWebViewConnectionBridgeScript = (input) => {
91
98
  var transport = {
92
99
  postMessage: function (message) {
93
100
  if (activeConnection !== connection) {
101
+ if (connection.isDisconnected) {
102
+ throw new Error(inactiveConnectionMessage);
103
+ }
94
104
  return;
95
105
  }
96
106
  postToNative(toConnectionMessage("message", connection, message));
@@ -111,8 +121,20 @@ export const buildWebViewConnectionBridgeScript = (input) => {
111
121
 
112
122
  return {
113
123
  connect: function () {
124
+ if (connection.isDisconnected || isClosed) {
125
+ return Promise.reject(new Error(inactiveConnectionMessage));
126
+ }
127
+ if (connection.connectPromise) {
128
+ return connection.connectPromise;
129
+ }
130
+ if (activeConnection && activeConnection !== connection) {
131
+ connection.isDisconnected = true;
132
+ var error = new Error(inactiveConnectionMessage);
133
+ reportConnectionError(connection, error);
134
+ return Promise.reject(error);
135
+ }
114
136
  activeConnection = connection;
115
- return new Promise(function (resolve, reject) {
137
+ var connectPromise = new Promise(function (resolve, reject) {
116
138
  connection.rejectConnect = reject;
117
139
  connection.resolveConnect = resolve;
118
140
  connection.connectTimeout = setTimeout(function () {
@@ -120,18 +142,25 @@ export const buildWebViewConnectionBridgeScript = (input) => {
120
142
  return;
121
143
  }
122
144
  activeConnection = null;
145
+ connection.isDisconnected = true;
146
+ isClosed = true;
123
147
  var error = new Error(connectTimeoutMessage);
124
148
  reportConnectionError(connection, error);
125
149
  connection.rejectConnect(error);
126
150
  connection.rejectConnect = null;
127
151
  connection.resolveConnect = null;
152
+ connection.connectPromise = null;
128
153
  connection.connectTimeout = null;
129
154
  }, ${connectTimeoutMs});
130
- bridge.activateConnection();
131
155
  });
156
+ connection.connectPromise = connectPromise;
157
+ bridge.activateConnection();
158
+ return connectPromise;
132
159
  },
133
160
  disconnect: function () {
161
+ connection.isDisconnected = true;
134
162
  if (activeConnection === connection) {
163
+ isClosed = true;
135
164
  activeConnection = null;
136
165
  connection.listener = null;
137
166
  if (connection.connectTimeout) {
@@ -151,6 +180,7 @@ export const buildWebViewConnectionBridgeScript = (input) => {
151
180
  connection.resolveConnect = null;
152
181
  }
153
182
  }
183
+ connection.connectPromise = null;
154
184
  return Promise.resolve();
155
185
  }
156
186
  };
@@ -182,6 +212,9 @@ export const buildWebViewConnectionBridgeScript = (input) => {
182
212
  connection.onActivated(connection.transport);
183
213
  } catch (error) {
184
214
  connection.isConnected = false;
215
+ connection.isDisconnected = true;
216
+ isClosed = true;
217
+ activeConnection = null;
185
218
  try {
186
219
  postToNative(toConnectionMessage("disconnected", connection));
187
220
  } catch {
@@ -197,6 +230,7 @@ export const buildWebViewConnectionBridgeScript = (input) => {
197
230
  clearTimeout(connection.connectTimeout);
198
231
  connection.connectTimeout = null;
199
232
  }
233
+ connection.connectPromise = null;
200
234
  return false;
201
235
  }
202
236
  if (connection.resolveConnect) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getuserfeedback/react-native",
3
- "version": "3.0.32",
3
+ "version": "3.0.33",
4
4
  "description": "getuserfeedback React Native SDK",
5
5
  "keywords": [
6
6
  "getuserfeedback",