@getuserfeedback/react-native 3.0.47 → 3.0.48
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,16 @@
|
|
|
1
|
+
import { type CoreCommandEnvelope } from "@getuserfeedback/protocol";
|
|
2
|
+
import { type EventCommandSettled } from "@getuserfeedback/protocol/internal/core-host-command-settlement";
|
|
3
|
+
import type { CoreWebViewHostConnection } from "./core-webview-host-controller.js";
|
|
4
|
+
export type NativeCoreCommandChannel = {
|
|
5
|
+
post: (envelope: CoreCommandEnvelope) => void;
|
|
6
|
+
subscribe: (listener: (message: EventCommandSettled) => void) => () => void;
|
|
7
|
+
};
|
|
8
|
+
type OwnedNativeCoreCommandChannel = {
|
|
9
|
+
channel: NativeCoreCommandChannel;
|
|
10
|
+
dispose: () => void;
|
|
11
|
+
};
|
|
12
|
+
export declare function createNativeCoreCommandChannel(input: {
|
|
13
|
+
coreConnection: CoreWebViewHostConnection;
|
|
14
|
+
onInvalidMessage?: (error: Error, value: unknown) => void;
|
|
15
|
+
}): OwnedNativeCoreCommandChannel;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { coreCommandEnvelopeSchema, } from "@getuserfeedback/protocol";
|
|
2
|
+
import { eventCommandSettledSchema, } from "@getuserfeedback/protocol/internal/core-host-command-settlement";
|
|
3
|
+
const DISPOSED_MESSAGE = "Core command channel is disposed";
|
|
4
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
5
|
+
export function createNativeCoreCommandChannel(input) {
|
|
6
|
+
const listeners = new Set();
|
|
7
|
+
let disposed = false;
|
|
8
|
+
const handleMessage = (value) => {
|
|
9
|
+
var _a;
|
|
10
|
+
if (disposed ||
|
|
11
|
+
!isRecord(value) ||
|
|
12
|
+
value.t !== "getuserfeedback:event:commandSettled") {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const parsed = eventCommandSettledSchema.safeParse(value);
|
|
16
|
+
if (!parsed.success) {
|
|
17
|
+
(_a = input.onInvalidMessage) === null || _a === void 0 ? void 0 : _a.call(input, parsed.error instanceof Error
|
|
18
|
+
? parsed.error
|
|
19
|
+
: new Error("Invalid core command settlement message"), value);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (parsed.data.gen !== input.coreConnection.generation) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
for (const listener of [...listeners]) {
|
|
26
|
+
listener(parsed.data);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const unsubscribeTransport = input.coreConnection.transport.subscribe(handleMessage);
|
|
30
|
+
const channel = {
|
|
31
|
+
post: (envelope) => {
|
|
32
|
+
if (disposed) {
|
|
33
|
+
throw new Error(DISPOSED_MESSAGE);
|
|
34
|
+
}
|
|
35
|
+
const validatedEnvelope = coreCommandEnvelopeSchema.parse(envelope);
|
|
36
|
+
input.coreConnection.transport.postMessage(validatedEnvelope);
|
|
37
|
+
},
|
|
38
|
+
subscribe: (listener) => {
|
|
39
|
+
if (disposed) {
|
|
40
|
+
return () => { };
|
|
41
|
+
}
|
|
42
|
+
listeners.add(listener);
|
|
43
|
+
return () => {
|
|
44
|
+
listeners.delete(listener);
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
channel,
|
|
50
|
+
dispose: () => {
|
|
51
|
+
if (disposed) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
disposed = true;
|
|
55
|
+
unsubscribeTransport();
|
|
56
|
+
listeners.clear();
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
2
|
import { type CoreWebViewHostProps } from "./core-webview-host.js";
|
|
3
|
+
import { type NativeCoreCommandChannel } from "./native-core-command-channel.js";
|
|
3
4
|
import { type NativeViewHostProjectionProps } from "./native-view-host-projection.js";
|
|
4
5
|
export type NativeRuntimeRootProps = {
|
|
5
6
|
coreUrl: string;
|
|
6
7
|
coreWebViewComponent?: CoreWebViewHostProps["webViewComponent"];
|
|
7
8
|
coreWebViewProps?: CoreWebViewHostProps["webViewProps"];
|
|
9
|
+
onCommandChannelChange?: (channel: NativeCoreCommandChannel | null) => void;
|
|
8
10
|
onError?: (error: Error) => void;
|
|
9
11
|
onInvalidMessage?: (error: Error, value: unknown) => void;
|
|
10
12
|
viewWebViewComponent?: NativeViewHostProjectionProps["webViewComponent"];
|
|
11
13
|
viewWebViewProps?: NativeViewHostProjectionProps["webViewProps"];
|
|
12
14
|
};
|
|
13
|
-
export declare function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, onError, onInvalidMessage, viewWebViewComponent, viewWebViewProps, }: NativeRuntimeRootProps): ReactElement;
|
|
15
|
+
export declare function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, onCommandChannelChange, onError, onInvalidMessage, viewWebViewComponent, viewWebViewProps, }: NativeRuntimeRootProps): ReactElement;
|
|
@@ -1,42 +1,91 @@
|
|
|
1
1
|
import { createElement, Fragment, useCallback, useEffect, useRef, useState, } from "react";
|
|
2
2
|
import { CoreWebViewHost, } from "./core-webview-host.js";
|
|
3
|
+
import { createNativeCoreCommandChannel, } from "./native-core-command-channel.js";
|
|
3
4
|
import { NativeViewHostProjection, } from "./native-view-host-projection.js";
|
|
4
5
|
import { createNativeViewRecordController, } from "./native-view-record-controller.js";
|
|
5
|
-
|
|
6
|
+
// TODO(architecture): Route protocol-owned init/bootstrap commands through the private native runtime channel without moving provider ownership.
|
|
7
|
+
// [from=private-native-command-bootstrap-composition] [to=protocol-owned-native-command-bootstrap-routing] [scope=slice] [principle=preserve-provider-runtime] [priority=high] [impact=high] [risk=high] [epic=runtime-neutral-view-host-orchestration] [epic_order=70]
|
|
8
|
+
export function NativeRuntimeRoot({ coreUrl, coreWebViewComponent, coreWebViewProps, onCommandChannelChange, onError, onInvalidMessage, viewWebViewComponent, viewWebViewProps, }) {
|
|
6
9
|
const [session, setSession] = useState(null);
|
|
7
10
|
const sessionRef = useRef(null);
|
|
8
11
|
const nextSessionKeyRef = useRef(0);
|
|
9
12
|
const onErrorRef = useRef(onError);
|
|
10
13
|
onErrorRef.current = onError;
|
|
14
|
+
const onCommandChannelChangeRef = useRef(onCommandChannelChange);
|
|
15
|
+
onCommandChannelChangeRef.current = onCommandChannelChange;
|
|
16
|
+
const onInvalidMessageRef = useRef(onInvalidMessage);
|
|
17
|
+
onInvalidMessageRef.current = onInvalidMessage;
|
|
18
|
+
const notifyCommandChannelObserver = useCallback((observer, channel) => {
|
|
19
|
+
var _a;
|
|
20
|
+
try {
|
|
21
|
+
observer === null || observer === void 0 ? void 0 : observer(channel);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
try {
|
|
25
|
+
(_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, error instanceof Error ? error : new Error(String(error)));
|
|
26
|
+
}
|
|
27
|
+
catch (_b) {
|
|
28
|
+
// Observer failures must not interrupt connection settlement.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}, []);
|
|
11
32
|
const handleConnected = useCallback((coreConnection) => {
|
|
12
33
|
const previousSession = sessionRef.current;
|
|
13
|
-
|
|
34
|
+
if (previousSession) {
|
|
35
|
+
sessionRef.current = null;
|
|
36
|
+
previousSession.commandChannelOwner.dispose();
|
|
37
|
+
previousSession.controller.dispose();
|
|
38
|
+
notifyCommandChannelObserver(previousSession.observer, null);
|
|
39
|
+
}
|
|
14
40
|
const controller = createNativeViewRecordController({
|
|
15
41
|
coreConnection,
|
|
16
42
|
onError: (error) => { var _a; return (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, error); },
|
|
17
43
|
});
|
|
44
|
+
const commandChannelOwner = createNativeCoreCommandChannel({
|
|
45
|
+
coreConnection,
|
|
46
|
+
onInvalidMessage: (error, value) => { var _a; return (_a = onInvalidMessageRef.current) === null || _a === void 0 ? void 0 : _a.call(onInvalidMessageRef, error, value); },
|
|
47
|
+
});
|
|
18
48
|
const nextSession = {
|
|
49
|
+
commandChannelOwner,
|
|
19
50
|
controller,
|
|
20
51
|
key: nextSessionKeyRef.current + 1,
|
|
52
|
+
observer: onCommandChannelChangeRef.current,
|
|
21
53
|
};
|
|
22
54
|
nextSessionKeyRef.current = nextSession.key;
|
|
23
55
|
sessionRef.current = nextSession;
|
|
24
56
|
setSession(nextSession);
|
|
25
|
-
|
|
57
|
+
notifyCommandChannelObserver(nextSession.observer, commandChannelOwner.channel);
|
|
58
|
+
}, [notifyCommandChannelObserver]);
|
|
26
59
|
const handleDisconnected = useCallback(() => {
|
|
27
60
|
const currentSession = sessionRef.current;
|
|
28
61
|
if (!currentSession) {
|
|
29
62
|
return;
|
|
30
63
|
}
|
|
31
|
-
currentSession.controller.dispose();
|
|
32
64
|
sessionRef.current = null;
|
|
65
|
+
currentSession.commandChannelOwner.dispose();
|
|
66
|
+
currentSession.controller.dispose();
|
|
33
67
|
setSession(null);
|
|
34
|
-
|
|
68
|
+
notifyCommandChannelObserver(currentSession.observer, null);
|
|
69
|
+
}, [notifyCommandChannelObserver]);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const currentSession = sessionRef.current;
|
|
72
|
+
if (!currentSession || currentSession.observer === onCommandChannelChange) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const previousObserver = currentSession.observer;
|
|
76
|
+
currentSession.observer = onCommandChannelChange;
|
|
77
|
+
notifyCommandChannelObserver(previousObserver, null);
|
|
78
|
+
notifyCommandChannelObserver(onCommandChannelChange, currentSession.commandChannelOwner.channel);
|
|
79
|
+
}, [notifyCommandChannelObserver, onCommandChannelChange]);
|
|
35
80
|
useEffect(() => () => {
|
|
36
81
|
const currentSession = sessionRef.current;
|
|
37
82
|
sessionRef.current = null;
|
|
38
|
-
|
|
39
|
-
|
|
83
|
+
if (currentSession) {
|
|
84
|
+
currentSession.commandChannelOwner.dispose();
|
|
85
|
+
currentSession.controller.dispose();
|
|
86
|
+
notifyCommandChannelObserver(currentSession.observer, null);
|
|
87
|
+
}
|
|
88
|
+
}, [notifyCommandChannelObserver]);
|
|
40
89
|
return createElement(Fragment, null, createElement(CoreWebViewHost, {
|
|
41
90
|
coreUrl,
|
|
42
91
|
onConnected: handleConnected,
|
|
@@ -38,5 +38,3 @@ export function NativeViewHostProjection({ controller, onInvalidMessage, webView
|
|
|
38
38
|
key: record.allocationId,
|
|
39
39
|
})));
|
|
40
40
|
}
|
|
41
|
-
// TODO(architecture): Prove command/bootstrap composition from the private native runtime root without switching provider ownership.
|
|
42
|
-
// [from=private-native-runtime-composition] [to=private-native-command-bootstrap-composition] [scope=slice] [principle=preserve-provider-runtime] [priority=high] [impact=high] [risk=high] [epic=runtime-neutral-view-host-orchestration] [epic_order=60]
|
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.
|
|
6
|
+
export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "3.0.48";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getuserfeedback/react-native",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.48",
|
|
4
4
|
"description": "getuserfeedback React Native SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"getuserfeedback",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"lint": "biome check ."
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@getuserfeedback/protocol": "^3.12.
|
|
48
|
-
"@getuserfeedback/sdk": "^0.10.
|
|
47
|
+
"@getuserfeedback/protocol": "^3.12.3",
|
|
48
|
+
"@getuserfeedback/sdk": "^0.10.13",
|
|
49
49
|
"robot3": "^1.2.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|