@getuserfeedback/react-native 3.0.33 → 3.0.35
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,23 @@
|
|
|
1
|
+
type NativeViewActivationState = "idle" | "prerendering" | "activation-requested" | "prerendering-activation-requested" | "prerendered" | "active";
|
|
2
|
+
type NativeViewRecord = Readonly<{
|
|
3
|
+
allocationId: number;
|
|
4
|
+
activationState: NativeViewActivationState;
|
|
5
|
+
generation: number;
|
|
6
|
+
instanceId: string;
|
|
7
|
+
srcdoc: string;
|
|
8
|
+
viewId: string;
|
|
9
|
+
}>;
|
|
10
|
+
export type NativeViewRecordController = {
|
|
11
|
+
dispose(): void;
|
|
12
|
+
getSnapshot(): readonly NativeViewRecord[];
|
|
13
|
+
handleCoreMessage(value: unknown): void;
|
|
14
|
+
markPrepared(input: {
|
|
15
|
+
allocationId: number;
|
|
16
|
+
viewId: string;
|
|
17
|
+
}): boolean;
|
|
18
|
+
subscribe(listener: () => void): () => void;
|
|
19
|
+
};
|
|
20
|
+
export declare function createNativeViewRecordController(input: {
|
|
21
|
+
generation: number;
|
|
22
|
+
}): NativeViewRecordController;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { coreViewHostActionSchema, } from "@getuserfeedback/protocol/internal/core-view-host-actions";
|
|
2
|
+
import { createViewActivationController, createViewPreparationRegistry, } from "./view-orchestration-runtime.js";
|
|
3
|
+
export function createNativeViewRecordController(input) {
|
|
4
|
+
const entries = new Map();
|
|
5
|
+
const listeners = new Set();
|
|
6
|
+
const pendingActivations = new Map();
|
|
7
|
+
const preparations = createViewPreparationRegistry();
|
|
8
|
+
let disposed = false;
|
|
9
|
+
let nextAllocationId = 0;
|
|
10
|
+
let snapshot = Object.freeze([]);
|
|
11
|
+
const notify = () => {
|
|
12
|
+
snapshot = Object.freeze(Array.from(entries.values(), toRecord));
|
|
13
|
+
for (const listener of listeners) {
|
|
14
|
+
listener();
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const ensurePendingActivation = (viewId) => {
|
|
18
|
+
const existing = pendingActivations.get(viewId);
|
|
19
|
+
if (existing) {
|
|
20
|
+
return existing;
|
|
21
|
+
}
|
|
22
|
+
const created = createViewActivationController();
|
|
23
|
+
pendingActivations.set(viewId, created);
|
|
24
|
+
return created;
|
|
25
|
+
};
|
|
26
|
+
const removeEntry = (entry) => {
|
|
27
|
+
if (entries.get(entry.viewId) !== entry) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
entries.delete(entry.viewId);
|
|
31
|
+
notify();
|
|
32
|
+
return true;
|
|
33
|
+
};
|
|
34
|
+
const handlePrerender = (action) => {
|
|
35
|
+
var _a;
|
|
36
|
+
if (entries.has(action.viewId)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const preparation = preparations.claim(action.viewId);
|
|
40
|
+
if (!preparation) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const activation = (_a = pendingActivations.get(action.viewId)) !== null && _a !== void 0 ? _a : createViewActivationController();
|
|
44
|
+
pendingActivations.delete(action.viewId);
|
|
45
|
+
activation.startPrerender();
|
|
46
|
+
nextAllocationId += 1;
|
|
47
|
+
const entry = {
|
|
48
|
+
activation,
|
|
49
|
+
allocationId: nextAllocationId,
|
|
50
|
+
generation: action.gen,
|
|
51
|
+
instanceId: action.instanceId,
|
|
52
|
+
preparation,
|
|
53
|
+
srcdoc: action.srcdoc,
|
|
54
|
+
viewId: action.viewId,
|
|
55
|
+
};
|
|
56
|
+
entries.set(action.viewId, entry);
|
|
57
|
+
preparation.addCleanup(() => {
|
|
58
|
+
removeEntry(entry);
|
|
59
|
+
});
|
|
60
|
+
notify();
|
|
61
|
+
};
|
|
62
|
+
const handleActivate = (action) => {
|
|
63
|
+
var _a;
|
|
64
|
+
const entry = entries.get(action.viewId);
|
|
65
|
+
const activation = (_a = entry === null || entry === void 0 ? void 0 : entry.activation) !== null && _a !== void 0 ? _a : ensurePendingActivation(action.viewId);
|
|
66
|
+
const previousState = activation.getState();
|
|
67
|
+
activation.requestActivation();
|
|
68
|
+
if (entry && activation.getState() !== previousState) {
|
|
69
|
+
notify();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const handleClose = (action) => {
|
|
73
|
+
if (preparations.cancel(action.viewId)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
pendingActivations.delete(action.viewId);
|
|
77
|
+
const entry = entries.get(action.viewId);
|
|
78
|
+
if (entry) {
|
|
79
|
+
removeEntry(entry);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
dispose() {
|
|
84
|
+
if (disposed) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
disposed = true;
|
|
88
|
+
const hadEntries = entries.size > 0;
|
|
89
|
+
entries.clear();
|
|
90
|
+
pendingActivations.clear();
|
|
91
|
+
preparations.cancelAll();
|
|
92
|
+
if (hadEntries) {
|
|
93
|
+
snapshot = Object.freeze([]);
|
|
94
|
+
for (const listener of listeners) {
|
|
95
|
+
listener();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
listeners.clear();
|
|
99
|
+
},
|
|
100
|
+
getSnapshot: () => snapshot,
|
|
101
|
+
handleCoreMessage(value) {
|
|
102
|
+
if (disposed) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const parsed = coreViewHostActionSchema.safeParse(value);
|
|
106
|
+
if (!parsed.success || parsed.data.gen !== input.generation) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
switch (parsed.data.t) {
|
|
110
|
+
case "getuserfeedback:action:prerenderView":
|
|
111
|
+
handlePrerender(parsed.data);
|
|
112
|
+
break;
|
|
113
|
+
case "getuserfeedback:action:activateView":
|
|
114
|
+
handleActivate(parsed.data);
|
|
115
|
+
break;
|
|
116
|
+
case "getuserfeedback:action:closeView":
|
|
117
|
+
handleClose(parsed.data);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
markPrepared({ allocationId, viewId }) {
|
|
122
|
+
if (disposed) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const entry = entries.get(viewId);
|
|
126
|
+
if (!entry || entry.allocationId !== allocationId) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (!entry.preparation.commit()) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const previousState = entry.activation.getState();
|
|
133
|
+
entry.activation.markPrerendered();
|
|
134
|
+
if (entry.activation.getState() !== previousState) {
|
|
135
|
+
notify();
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
},
|
|
139
|
+
subscribe(listener) {
|
|
140
|
+
if (disposed) {
|
|
141
|
+
return () => { };
|
|
142
|
+
}
|
|
143
|
+
listeners.add(listener);
|
|
144
|
+
return () => listeners.delete(listener);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function toRecord(entry) {
|
|
149
|
+
return Object.freeze({
|
|
150
|
+
activationState: entry.activation.getState(),
|
|
151
|
+
allocationId: entry.allocationId,
|
|
152
|
+
generation: entry.generation,
|
|
153
|
+
instanceId: entry.instanceId,
|
|
154
|
+
srcdoc: entry.srcdoc,
|
|
155
|
+
viewId: entry.viewId,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// TODO(migration): Subscribe this controller to the native core transport, render its identity-safe records through ViewWebViewHost, and complete preparation only after protocol-owned view readiness is observed. Keep the provider runtime active during this proof.
|
|
159
|
+
// [from=private-native-view-allocation-state] [to=private-native-view-readiness-routing] [scope=slice] [priority=high] [impact=high] [risk=high] [epic=runtime-neutral-view-host-orchestration] [epic_order=30]
|
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.35";
|
|
@@ -79,6 +79,90 @@ function readViewActivationState(value) {
|
|
|
79
79
|
}
|
|
80
80
|
return value;
|
|
81
81
|
}
|
|
82
|
+
// ../view-orchestration/src/view-preparation-registry.ts
|
|
83
|
+
class ViewPreparationCancelledError extends Error {
|
|
84
|
+
viewId;
|
|
85
|
+
constructor(viewId) {
|
|
86
|
+
super(`View preparation for "${viewId}" was cancelled`);
|
|
87
|
+
this.name = "AbortError";
|
|
88
|
+
this.viewId = viewId;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function runCleanups(entry) {
|
|
92
|
+
for (const cleanup of Array.from(entry.cleanups)) {
|
|
93
|
+
entry.cleanups.delete(cleanup);
|
|
94
|
+
try {
|
|
95
|
+
cleanup();
|
|
96
|
+
} catch {}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function createViewPreparationRegistry() {
|
|
100
|
+
const entries = new Map;
|
|
101
|
+
return {
|
|
102
|
+
claim(viewId) {
|
|
103
|
+
if (entries.has(viewId)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const entry = {
|
|
107
|
+
controller: new AbortController,
|
|
108
|
+
cleanups: new Set
|
|
109
|
+
};
|
|
110
|
+
entries.set(viewId, entry);
|
|
111
|
+
return {
|
|
112
|
+
signal: entry.controller.signal,
|
|
113
|
+
addCleanup: (cleanup) => {
|
|
114
|
+
if (entry.controller.signal.aborted) {
|
|
115
|
+
try {
|
|
116
|
+
cleanup();
|
|
117
|
+
} catch {}
|
|
118
|
+
return () => {};
|
|
119
|
+
}
|
|
120
|
+
entry.cleanups.add(cleanup);
|
|
121
|
+
return () => entry.cleanups.delete(cleanup);
|
|
122
|
+
},
|
|
123
|
+
commit: () => {
|
|
124
|
+
if (entry.controller.signal.aborted || entries.get(viewId) !== entry) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
entries.delete(viewId);
|
|
128
|
+
entry.cleanups.clear();
|
|
129
|
+
return true;
|
|
130
|
+
},
|
|
131
|
+
isCancelled: () => entry.controller.signal.aborted,
|
|
132
|
+
release: () => {
|
|
133
|
+
if (entries.get(viewId) === entry) {
|
|
134
|
+
entries.delete(viewId);
|
|
135
|
+
}
|
|
136
|
+
runCleanups(entry);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
cancel(viewId) {
|
|
141
|
+
const entry = entries.get(viewId);
|
|
142
|
+
if (!entry) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
entries.delete(viewId);
|
|
146
|
+
entry.controller.abort(new ViewPreparationCancelledError(viewId));
|
|
147
|
+
runCleanups(entry);
|
|
148
|
+
return true;
|
|
149
|
+
},
|
|
150
|
+
cancelAll() {
|
|
151
|
+
const viewIds = Array.from(entries.keys());
|
|
152
|
+
for (const viewId of viewIds) {
|
|
153
|
+
const entry = entries.get(viewId);
|
|
154
|
+
entries.delete(viewId);
|
|
155
|
+
entry?.controller.abort(new ViewPreparationCancelledError(viewId));
|
|
156
|
+
if (entry) {
|
|
157
|
+
runCleanups(entry);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return viewIds;
|
|
161
|
+
},
|
|
162
|
+
has: (viewId) => entries.has(viewId),
|
|
163
|
+
isEmpty: () => entries.size === 0
|
|
164
|
+
};
|
|
165
|
+
}
|
|
82
166
|
// src/view-orchestration-runtime.ts
|
|
83
167
|
function createActiveViewSnapshotListController2() {
|
|
84
168
|
return createActiveViewSnapshotListController();
|
|
@@ -86,7 +170,11 @@ function createActiveViewSnapshotListController2() {
|
|
|
86
170
|
function createViewActivationController2() {
|
|
87
171
|
return createViewActivationController();
|
|
88
172
|
}
|
|
173
|
+
function createViewPreparationRegistry2() {
|
|
174
|
+
return createViewPreparationRegistry();
|
|
175
|
+
}
|
|
89
176
|
export {
|
|
177
|
+
createViewPreparationRegistry2 as createViewPreparationRegistry,
|
|
90
178
|
createViewActivationController2 as createViewActivationController,
|
|
91
179
|
createActiveViewSnapshotListController2 as createActiveViewSnapshotListController
|
|
92
180
|
};
|
|
@@ -50,5 +50,3 @@ function ViewWebViewHostDocument({ generation, onConnected, onDisconnected, onIn
|
|
|
50
50
|
export function ViewWebViewHost(props) {
|
|
51
51
|
return createElement(ViewWebViewHostDocument, Object.assign(Object.assign({}, props), { key: JSON.stringify([props.viewId, props.generation, props.srcdoc]) }));
|
|
52
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]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getuserfeedback/react-native",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.35",
|
|
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.
|
|
48
|
-
"@getuserfeedback/sdk": "^0.10.
|
|
47
|
+
"@getuserfeedback/protocol": "^3.8.0",
|
|
48
|
+
"@getuserfeedback/sdk": "^0.10.4",
|
|
49
49
|
"robot3": "^1.2.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|