@hypen-space/core 0.4.956 → 0.5.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 +5 -8
- package/dist/app.js +13 -1168
- package/dist/app.js.map +3 -9
- package/dist/chunk-00zt3w6e.js +58 -0
- package/dist/chunk-00zt3w6e.js.map +10 -0
- package/dist/chunk-3zvyth50.js +87 -0
- package/dist/chunk-3zvyth50.js.map +10 -0
- package/dist/chunk-5gtx3pza.js +11 -0
- package/dist/chunk-5gtx3pza.js.map +9 -0
- package/dist/chunk-7222131a.js +191 -0
- package/dist/chunk-7222131a.js.map +10 -0
- package/dist/chunk-avchpwhf.js +200 -0
- package/dist/chunk-avchpwhf.js.map +10 -0
- package/dist/chunk-ec0tgy9a.js +154 -0
- package/dist/chunk-ec0tgy9a.js.map +10 -0
- package/dist/chunk-fnss4dm2.js +479 -0
- package/dist/chunk-fnss4dm2.js.map +10 -0
- package/dist/chunk-g2gz9pgg.js +133 -0
- package/dist/chunk-g2gz9pgg.js.map +10 -0
- package/dist/chunk-g72v6mmf.js +341 -0
- package/dist/chunk-g72v6mmf.js.map +10 -0
- package/dist/chunk-g7g3jxty.js +351 -0
- package/dist/chunk-g7g3jxty.js.map +10 -0
- package/dist/chunk-g830re2c.js +50 -0
- package/dist/chunk-g830re2c.js.map +10 -0
- package/dist/chunk-gtwh1sg7.js +127 -0
- package/dist/chunk-gtwh1sg7.js.map +10 -0
- package/dist/chunk-k12cwfst.js +72 -0
- package/dist/chunk-k12cwfst.js.map +10 -0
- package/dist/chunk-mbqqsz6r.js +128 -0
- package/dist/chunk-mbqqsz6r.js.map +10 -0
- package/dist/chunk-ne632swm.js +209 -0
- package/dist/chunk-ne632swm.js.map +10 -0
- package/dist/chunk-p3pj1bax.js +206 -0
- package/dist/chunk-p3pj1bax.js.map +10 -0
- package/dist/chunk-vshdk2pw.js +56 -0
- package/dist/chunk-vshdk2pw.js.map +10 -0
- package/dist/chunk-wp2ebn2m.js +51 -0
- package/dist/chunk-wp2ebn2m.js.map +10 -0
- package/dist/chunk-x4qv4jm9.js +88 -0
- package/dist/chunk-x4qv4jm9.js.map +10 -0
- package/dist/chunk-ycxp4drd.js +211 -0
- package/dist/chunk-ycxp4drd.js.map +10 -0
- package/dist/components/builtin.js +14 -1246
- package/dist/components/builtin.js.map +3 -10
- package/dist/context.js +7 -362
- package/dist/context.js.map +3 -6
- package/dist/datasource.js +5 -76
- package/dist/datasource.js.map +3 -4
- package/dist/disposable.js +20 -367
- package/dist/disposable.js.map +3 -5
- package/dist/engine-base.d.ts +38 -2
- package/dist/engine-base.js +45 -412
- package/dist/engine-base.js.map +4 -6
- package/dist/events.js +7 -285
- package/dist/events.js.map +3 -5
- package/dist/hypen.js +8 -71
- package/dist/hypen.js.map +3 -4
- package/dist/index.browser.js +58 -2152
- package/dist/index.browser.js.map +3 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.js +132 -3058
- package/dist/index.js.map +4 -22
- package/dist/logger.js +16 -220
- package/dist/logger.js.map +3 -4
- package/dist/managed-router.js +12 -1367
- package/dist/managed-router.js.map +3 -10
- package/dist/portable.js +6 -70
- package/dist/portable.js.map +3 -4
- package/dist/remote/client.js +9 -1010
- package/dist/remote/client.js.map +3 -8
- package/dist/remote/index.d.ts +2 -0
- package/dist/remote/index.js +511 -1066
- package/dist/remote/index.js.map +4 -9
- package/dist/remote/remote-session.d.ts +245 -0
- package/dist/remote/session.js +5 -147
- package/dist/remote/session.js.map +3 -4
- package/dist/renderer.js +7 -271
- package/dist/renderer.js.map +3 -5
- package/dist/resolver.js +5 -484
- package/dist/resolver.js.map +3 -5
- package/dist/result.js +25 -211
- package/dist/result.js.map +3 -4
- package/dist/retry.js +10 -336
- package/dist/retry.js.map +3 -5
- package/dist/router.js +9 -807
- package/dist/router.js.map +3 -8
- package/dist/state.js +10 -274
- package/dist/state.js.map +3 -5
- package/dist/validate.d.ts +19 -0
- package/package.json +13 -1
- package/src/engine-base.ts +87 -4
- package/src/index.ts +5 -0
- package/src/remote/index.ts +16 -0
- package/src/remote/remote-session.ts +998 -0
- package/src/validate.ts +37 -0
package/dist/remote/index.js
CHANGED
|
@@ -1,1139 +1,584 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
function isErr(result) {
|
|
35
|
-
return !result.ok;
|
|
36
|
-
}
|
|
37
|
-
async function fromPromise(promise, mapError) {
|
|
38
|
-
try {
|
|
39
|
-
const value = await promise;
|
|
40
|
-
return Ok(value);
|
|
41
|
-
} catch (e) {
|
|
42
|
-
if (mapError) {
|
|
43
|
-
return Err(mapError(e));
|
|
44
|
-
}
|
|
45
|
-
return Err(e);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function fromTry(fn, mapError) {
|
|
49
|
-
try {
|
|
50
|
-
return Ok(fn());
|
|
51
|
-
} catch (e) {
|
|
52
|
-
if (mapError) {
|
|
53
|
-
return Err(mapError(e));
|
|
54
|
-
}
|
|
55
|
-
return Err(e);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function map(result, fn) {
|
|
59
|
-
if (result.ok) {
|
|
60
|
-
return Ok(fn(result.value));
|
|
61
|
-
}
|
|
62
|
-
return result;
|
|
63
|
-
}
|
|
64
|
-
function mapErr(result, fn) {
|
|
65
|
-
if (!result.ok) {
|
|
66
|
-
return Err(fn(result.error));
|
|
67
|
-
}
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
function flatMap(result, fn) {
|
|
71
|
-
if (result.ok) {
|
|
72
|
-
return fn(result.value);
|
|
73
|
-
}
|
|
74
|
-
return result;
|
|
75
|
-
}
|
|
76
|
-
function unwrap(result) {
|
|
77
|
-
if (result.ok) {
|
|
78
|
-
return result.value;
|
|
79
|
-
}
|
|
80
|
-
throw result.error;
|
|
81
|
-
}
|
|
82
|
-
function unwrapOr(result, defaultValue) {
|
|
83
|
-
if (result.ok) {
|
|
84
|
-
return result.value;
|
|
85
|
-
}
|
|
86
|
-
return defaultValue;
|
|
87
|
-
}
|
|
88
|
-
function unwrapOrElse(result, fn) {
|
|
89
|
-
if (result.ok) {
|
|
90
|
-
return result.value;
|
|
91
|
-
}
|
|
92
|
-
return fn(result.error);
|
|
93
|
-
}
|
|
94
|
-
function match(result, handlers) {
|
|
95
|
-
if (result.ok) {
|
|
96
|
-
return handlers.ok(result.value);
|
|
97
|
-
}
|
|
98
|
-
return handlers.err(result.error);
|
|
99
|
-
}
|
|
100
|
-
function all(results) {
|
|
101
|
-
const values = [];
|
|
102
|
-
for (const result of results) {
|
|
103
|
-
if (!result.ok) {
|
|
104
|
-
return result;
|
|
105
|
-
}
|
|
106
|
-
values.push(result.value);
|
|
107
|
-
}
|
|
108
|
-
return Ok(values);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
class HypenError extends Error {
|
|
112
|
-
code;
|
|
113
|
-
context;
|
|
114
|
-
cause;
|
|
115
|
-
constructor(code, message, options) {
|
|
116
|
-
super(message);
|
|
117
|
-
this.name = "HypenError";
|
|
118
|
-
this.code = code;
|
|
119
|
-
this.context = options?.context;
|
|
120
|
-
this.cause = options?.cause;
|
|
121
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
class ActionError extends HypenError {
|
|
126
|
-
actionName;
|
|
127
|
-
constructor(actionName, cause) {
|
|
128
|
-
super("ACTION_ERROR", `Action handler "${actionName}" failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
|
|
129
|
-
context: { actionName },
|
|
130
|
-
cause: cause instanceof Error ? cause : undefined
|
|
131
|
-
});
|
|
132
|
-
this.name = "ActionError";
|
|
133
|
-
this.actionName = actionName;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
class ConnectionError extends HypenError {
|
|
138
|
-
url;
|
|
139
|
-
attempt;
|
|
140
|
-
constructor(url, cause, attempt) {
|
|
141
|
-
super("CONNECTION_ERROR", `Connection to "${url}" failed${attempt ? ` (attempt ${attempt})` : ""}: ${cause instanceof Error ? cause.message : String(cause)}`, {
|
|
142
|
-
context: { url, attempt },
|
|
143
|
-
cause: cause instanceof Error ? cause : undefined
|
|
144
|
-
});
|
|
145
|
-
this.name = "ConnectionError";
|
|
146
|
-
this.url = url;
|
|
147
|
-
this.attempt = attempt;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
SessionManager
|
|
3
|
+
} from "../chunk-gtwh1sg7.js";
|
|
4
|
+
import {
|
|
5
|
+
HypenGlobalContext
|
|
6
|
+
} from "../chunk-3zvyth50.js";
|
|
7
|
+
import"../chunk-k12cwfst.js";
|
|
8
|
+
import {
|
|
9
|
+
RemoteEngine
|
|
10
|
+
} from "../chunk-g7g3jxty.js";
|
|
11
|
+
import"../chunk-g2gz9pgg.js";
|
|
12
|
+
import {
|
|
13
|
+
HypenRouter
|
|
14
|
+
} from "../chunk-p3pj1bax.js";
|
|
15
|
+
import"../chunk-ec0tgy9a.js";
|
|
16
|
+
import {
|
|
17
|
+
ManagedRouter
|
|
18
|
+
} from "../chunk-ne632swm.js";
|
|
19
|
+
import {
|
|
20
|
+
HypenApp,
|
|
21
|
+
HypenModuleInstance
|
|
22
|
+
} from "../chunk-fnss4dm2.js";
|
|
23
|
+
import"../chunk-vshdk2pw.js";
|
|
24
|
+
import"../chunk-7222131a.js";
|
|
25
|
+
import"../chunk-ycxp4drd.js";
|
|
26
|
+
import"../chunk-g830re2c.js";
|
|
27
|
+
import {
|
|
28
|
+
frameworkLoggers
|
|
29
|
+
} from "../chunk-avchpwhf.js";
|
|
30
|
+
import"../chunk-5gtx3pza.js";
|
|
31
|
+
// src/remote/remote-session.ts
|
|
32
|
+
var log = frameworkLoggers.remote;
|
|
33
|
+
var nextSessionCounter = 1;
|
|
150
34
|
|
|
151
|
-
class
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
35
|
+
class RemoteSession {
|
|
36
|
+
id;
|
|
37
|
+
connectedAt;
|
|
38
|
+
host;
|
|
39
|
+
transport;
|
|
40
|
+
socketHandle;
|
|
41
|
+
engine;
|
|
42
|
+
_moduleInstance = null;
|
|
43
|
+
_sessionId = null;
|
|
44
|
+
_helloReceived = false;
|
|
45
|
+
_stateSubscribed = false;
|
|
46
|
+
_revision = 0;
|
|
47
|
+
_destroyed = false;
|
|
48
|
+
helloTimeout = null;
|
|
49
|
+
_autoManagedRouter = null;
|
|
50
|
+
autoRouterEnabled = true;
|
|
51
|
+
ready;
|
|
52
|
+
closed;
|
|
53
|
+
_resolveReady;
|
|
54
|
+
_resolveClosed;
|
|
55
|
+
constructor(host, transport, options = {}) {
|
|
56
|
+
this.host = host;
|
|
57
|
+
this.transport = transport;
|
|
58
|
+
this.id = options.clientId ?? `client_${nextSessionCounter++}`;
|
|
59
|
+
this.connectedAt = new Date;
|
|
60
|
+
this.socketHandle = options.socketHandle;
|
|
61
|
+
this.ready = new Promise((resolve) => {
|
|
62
|
+
this._resolveReady = resolve;
|
|
157
63
|
});
|
|
158
|
-
this.
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
class ParseError extends HypenError {
|
|
164
|
-
source;
|
|
165
|
-
constructor(message, source, cause) {
|
|
166
|
-
super("PARSE_ERROR", message, {
|
|
167
|
-
context: { source },
|
|
168
|
-
cause: cause instanceof Error ? cause : undefined
|
|
64
|
+
this.closed = new Promise((resolve) => {
|
|
65
|
+
this._resolveClosed = resolve;
|
|
169
66
|
});
|
|
170
|
-
this.
|
|
171
|
-
this.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (err && typeof err === "object" && "message" in err && "type" in err) {
|
|
185
|
-
const structured = err;
|
|
186
|
-
switch (structured.type) {
|
|
187
|
-
case "parseError":
|
|
188
|
-
return new ParseError(structured.message);
|
|
189
|
-
case "stateError":
|
|
190
|
-
return new StateError(structured.message);
|
|
191
|
-
case "renderError":
|
|
192
|
-
return new RenderError(structured.message);
|
|
193
|
-
case "actionError":
|
|
194
|
-
return new HypenError("ACTION_ERROR", structured.message);
|
|
195
|
-
default:
|
|
196
|
-
return new HypenError("UNKNOWN_ERROR", structured.message);
|
|
67
|
+
this.engine = host.createEngine();
|
|
68
|
+
this.engine.init().catch((err) => log.error("Engine init failed:", err));
|
|
69
|
+
this.setupComponentResolver();
|
|
70
|
+
if (Object.keys(host.resources).length > 0) {
|
|
71
|
+
this.engine.registerResources(host.resources);
|
|
72
|
+
}
|
|
73
|
+
log.info(`Session ${this.id} created, engine initialized`);
|
|
74
|
+
const graceMs = options.helloGraceMs ?? 1000;
|
|
75
|
+
if (graceMs !== null) {
|
|
76
|
+
this.helloTimeout = setTimeout(() => {
|
|
77
|
+
if (!this._helloReceived) {
|
|
78
|
+
this.initializeSession(undefined, undefined).catch((err) => log.error("Error initializing legacy session:", err));
|
|
79
|
+
}
|
|
80
|
+
}, graceMs);
|
|
197
81
|
}
|
|
198
82
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return new ParseError(message);
|
|
83
|
+
get sessionId() {
|
|
84
|
+
return this._sessionId;
|
|
202
85
|
}
|
|
203
|
-
|
|
204
|
-
return
|
|
86
|
+
get moduleInstance() {
|
|
87
|
+
return this._moduleInstance;
|
|
205
88
|
}
|
|
206
|
-
|
|
207
|
-
return
|
|
208
|
-
}
|
|
209
|
-
return new RenderError(message);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// src/logger.ts
|
|
213
|
-
var LOG_LEVEL_ORDER = {
|
|
214
|
-
debug: 0,
|
|
215
|
-
info: 1,
|
|
216
|
-
warn: 2,
|
|
217
|
-
error: 3,
|
|
218
|
-
none: 4
|
|
219
|
-
};
|
|
220
|
-
var LOG_LEVEL_COLORS = {
|
|
221
|
-
debug: "\x1B[36m",
|
|
222
|
-
info: "\x1B[32m",
|
|
223
|
-
warn: "\x1B[33m",
|
|
224
|
-
error: "\x1B[31m"
|
|
225
|
-
};
|
|
226
|
-
var RESET_COLOR = "\x1B[0m";
|
|
227
|
-
function isProduction() {
|
|
228
|
-
if (typeof process !== "undefined" && process.env) {
|
|
229
|
-
return false;
|
|
89
|
+
get helloReceived() {
|
|
90
|
+
return this._helloReceived;
|
|
230
91
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
var config = {
|
|
234
|
-
level: isProduction() ? "error" : "info",
|
|
235
|
-
colors: true,
|
|
236
|
-
timestamps: false
|
|
237
|
-
};
|
|
238
|
-
function setLogLevel(level) {
|
|
239
|
-
config.level = level;
|
|
240
|
-
}
|
|
241
|
-
function getLogLevel() {
|
|
242
|
-
return config.level;
|
|
243
|
-
}
|
|
244
|
-
function configureLogger(options) {
|
|
245
|
-
config = { ...config, ...options };
|
|
246
|
-
}
|
|
247
|
-
function enableLogging() {
|
|
248
|
-
config.level = "debug";
|
|
249
|
-
}
|
|
250
|
-
function disableLogging() {
|
|
251
|
-
config.level = "none";
|
|
252
|
-
}
|
|
253
|
-
function setDebugMode(enabled) {
|
|
254
|
-
config.level = enabled ? "debug" : "error";
|
|
255
|
-
}
|
|
256
|
-
function isDebugMode() {
|
|
257
|
-
return config.level === "debug";
|
|
258
|
-
}
|
|
259
|
-
function shouldLog(level) {
|
|
260
|
-
return LOG_LEVEL_ORDER[level] >= LOG_LEVEL_ORDER[config.level];
|
|
261
|
-
}
|
|
262
|
-
function formatTag(tag, level) {
|
|
263
|
-
const timestamp = config.timestamps ? `${new Date().toISOString()} ` : "";
|
|
264
|
-
if (config.colors && level !== "none") {
|
|
265
|
-
const color = LOG_LEVEL_COLORS[level];
|
|
266
|
-
return `${timestamp}${color}[${tag}]${RESET_COLOR}`;
|
|
92
|
+
get stateSubscribed() {
|
|
93
|
+
return this._stateSubscribed;
|
|
267
94
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
class Logger {
|
|
272
|
-
tag;
|
|
273
|
-
constructor(tag) {
|
|
274
|
-
this.tag = tag;
|
|
95
|
+
get revision() {
|
|
96
|
+
return this._revision;
|
|
275
97
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
return;
|
|
279
|
-
if (config.handler) {
|
|
280
|
-
config.handler.debug(this.tag, ...args);
|
|
281
|
-
} else {
|
|
282
|
-
console.log(formatTag(this.tag, "debug"), ...args);
|
|
283
|
-
}
|
|
98
|
+
get isDestroyed() {
|
|
99
|
+
return this._destroyed;
|
|
284
100
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
console.info(formatTag(this.tag, "info"), ...args);
|
|
292
|
-
}
|
|
101
|
+
toRemoteClient() {
|
|
102
|
+
return {
|
|
103
|
+
id: this.id,
|
|
104
|
+
socket: this.socketHandle,
|
|
105
|
+
connectedAt: this.connectedAt
|
|
106
|
+
};
|
|
293
107
|
}
|
|
294
|
-
|
|
295
|
-
if (
|
|
108
|
+
async receive(raw) {
|
|
109
|
+
if (this._destroyed)
|
|
296
110
|
return;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
111
|
+
let msg;
|
|
112
|
+
try {
|
|
113
|
+
if (typeof raw === "string" || raw instanceof Buffer) {
|
|
114
|
+
msg = JSON.parse(raw.toString());
|
|
115
|
+
} else {
|
|
116
|
+
msg = raw;
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
log.error(`Invalid message on session ${this.id}:`, err);
|
|
305
120
|
return;
|
|
306
|
-
if (config.handler) {
|
|
307
|
-
config.handler.error(this.tag, ...args);
|
|
308
|
-
} else {
|
|
309
|
-
console.error(formatTag(this.tag, "error"), ...args);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
time(label, fn) {
|
|
313
|
-
if (!shouldLog("debug")) {
|
|
314
|
-
return fn();
|
|
315
121
|
}
|
|
316
|
-
const start = performance.now();
|
|
317
122
|
try {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
123
|
+
switch (msg.type) {
|
|
124
|
+
case "hello": {
|
|
125
|
+
const hello = msg;
|
|
126
|
+
await this.initializeSession(hello.sessionId, hello.props);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case "dispatchAction": {
|
|
130
|
+
const action = msg;
|
|
131
|
+
this.engine.dispatchAction(action.action, action.payload);
|
|
132
|
+
if (this.host.syncActions) {
|
|
133
|
+
for (const other of this.host.otherSessions(this)) {
|
|
134
|
+
if (!other._helloReceived)
|
|
135
|
+
continue;
|
|
136
|
+
other.engine.dispatchAction(action.action, action.payload);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "updateState": {
|
|
142
|
+
const stateMsg = msg;
|
|
143
|
+
this._moduleInstance?.updateState(stateMsg.state);
|
|
144
|
+
if (this.host.syncActions) {
|
|
145
|
+
for (const other of this.host.otherSessions(this)) {
|
|
146
|
+
if (!other._helloReceived || !other._moduleInstance)
|
|
147
|
+
continue;
|
|
148
|
+
other._moduleInstance.updateState(stateMsg.state);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "subscribeState": {
|
|
154
|
+
this._stateSubscribed = true;
|
|
155
|
+
log.info(`Session ${this.id} subscribed to state updates`);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
default:
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
log.error(`Error handling message on session ${this.id}:`, err);
|
|
322
163
|
}
|
|
323
164
|
}
|
|
324
|
-
|
|
325
|
-
if (
|
|
326
|
-
return
|
|
327
|
-
}
|
|
328
|
-
const start = performance.now();
|
|
165
|
+
send(message) {
|
|
166
|
+
if (this._destroyed)
|
|
167
|
+
return;
|
|
329
168
|
try {
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
this.debug(`${label}: ${duration.toFixed(2)}ms`);
|
|
169
|
+
this.transport.send(message);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
log.error(`Failed to send message on session ${this.id}:`, err);
|
|
334
172
|
}
|
|
335
173
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
debugIf(condition, ...args) {
|
|
340
|
-
if (condition)
|
|
341
|
-
this.debug(...args);
|
|
342
|
-
}
|
|
343
|
-
warnIf(condition, ...args) {
|
|
344
|
-
if (condition)
|
|
345
|
-
this.warn(...args);
|
|
346
|
-
}
|
|
347
|
-
errorIf(condition, ...args) {
|
|
348
|
-
if (condition)
|
|
349
|
-
this.error(...args);
|
|
350
|
-
}
|
|
351
|
-
loggedOnce = new Set;
|
|
352
|
-
warnOnce(key, ...args) {
|
|
353
|
-
if (this.loggedOnce.has(key))
|
|
354
|
-
return;
|
|
355
|
-
this.loggedOnce.add(key);
|
|
356
|
-
this.warn(...args);
|
|
357
|
-
}
|
|
358
|
-
debugOnce(key, ...args) {
|
|
359
|
-
if (this.loggedOnce.has(key))
|
|
174
|
+
expireAndClose(reason) {
|
|
175
|
+
if (!this._sessionId) {
|
|
176
|
+
this.transport.close(1000, "Session closed");
|
|
360
177
|
return;
|
|
361
|
-
this.loggedOnce.add(key);
|
|
362
|
-
this.debug(...args);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
function createLogger(tag) {
|
|
366
|
-
return new Logger(tag);
|
|
367
|
-
}
|
|
368
|
-
var logger = createLogger("Hypen");
|
|
369
|
-
var log = {
|
|
370
|
-
debug: (tag, ...args) => {
|
|
371
|
-
if (!shouldLog("debug"))
|
|
372
|
-
return;
|
|
373
|
-
console.log(formatTag(tag, "debug"), ...args);
|
|
374
|
-
},
|
|
375
|
-
info: (tag, ...args) => {
|
|
376
|
-
if (!shouldLog("info"))
|
|
377
|
-
return;
|
|
378
|
-
console.info(formatTag(tag, "info"), ...args);
|
|
379
|
-
},
|
|
380
|
-
warn: (tag, ...args) => {
|
|
381
|
-
if (!shouldLog("warn"))
|
|
382
|
-
return;
|
|
383
|
-
console.warn(formatTag(tag, "warn"), ...args);
|
|
384
|
-
},
|
|
385
|
-
error: (tag, ...args) => {
|
|
386
|
-
if (!shouldLog("error"))
|
|
387
|
-
return;
|
|
388
|
-
console.error(formatTag(tag, "error"), ...args);
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
var frameworkLoggers = {
|
|
392
|
-
hypen: createLogger("Hypen"),
|
|
393
|
-
engine: createLogger("Engine"),
|
|
394
|
-
router: createLogger("Router"),
|
|
395
|
-
state: createLogger("State"),
|
|
396
|
-
events: createLogger("Events"),
|
|
397
|
-
remote: createLogger("Remote"),
|
|
398
|
-
renderer: createLogger("Renderer"),
|
|
399
|
-
module: createLogger("Module"),
|
|
400
|
-
lifecycle: createLogger("Lifecycle"),
|
|
401
|
-
loader: createLogger("Loader"),
|
|
402
|
-
context: createLogger("Context"),
|
|
403
|
-
discovery: createLogger("Discovery"),
|
|
404
|
-
plugin: createLogger("Plugin"),
|
|
405
|
-
canvas: createLogger("Canvas"),
|
|
406
|
-
debug: createLogger("Debug")
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
// src/disposable.ts
|
|
410
|
-
var log2 = frameworkLoggers.lifecycle;
|
|
411
|
-
function isDisposable(obj) {
|
|
412
|
-
return obj !== null && typeof obj === "object" && "dispose" in obj && typeof obj.dispose === "function";
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
class DisposableStack {
|
|
416
|
-
stack = [];
|
|
417
|
-
disposed = false;
|
|
418
|
-
add(disposable) {
|
|
419
|
-
if (this.disposed) {
|
|
420
|
-
disposable.dispose();
|
|
421
|
-
return disposable;
|
|
422
178
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
this.add({ dispose: () => dispose(value) });
|
|
431
|
-
return value;
|
|
179
|
+
const expired = {
|
|
180
|
+
type: "sessionExpired",
|
|
181
|
+
sessionId: this._sessionId,
|
|
182
|
+
reason
|
|
183
|
+
};
|
|
184
|
+
this.send(expired);
|
|
185
|
+
this.transport.close(1000, "Session " + reason);
|
|
432
186
|
}
|
|
433
|
-
|
|
434
|
-
if (this.
|
|
187
|
+
async destroy() {
|
|
188
|
+
if (this._destroyed)
|
|
435
189
|
return;
|
|
436
|
-
this.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
get isDisposed() {
|
|
447
|
-
return this.disposed;
|
|
448
|
-
}
|
|
449
|
-
get size() {
|
|
450
|
-
return this.stack.length;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
function disposableListener(target, event, handler, options) {
|
|
454
|
-
target.addEventListener(event, handler, options);
|
|
455
|
-
return {
|
|
456
|
-
dispose: () => target.removeEventListener(event, handler, options)
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
function disposableTimeout(callback, ms) {
|
|
460
|
-
const id = setTimeout(callback, ms);
|
|
461
|
-
return {
|
|
462
|
-
id,
|
|
463
|
-
dispose: () => clearTimeout(id)
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
function disposableInterval(callback, ms) {
|
|
467
|
-
const id = setInterval(callback, ms);
|
|
468
|
-
return {
|
|
469
|
-
id,
|
|
470
|
-
dispose: () => clearInterval(id)
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
function disposableWebSocket(ws) {
|
|
474
|
-
return {
|
|
475
|
-
dispose: () => {
|
|
476
|
-
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
477
|
-
ws.close();
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
function disposableAbortController() {
|
|
483
|
-
const controller = new AbortController;
|
|
484
|
-
return {
|
|
485
|
-
controller,
|
|
486
|
-
signal: controller.signal,
|
|
487
|
-
dispose: () => controller.abort()
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
function disposableSubscription(unsubscribe) {
|
|
491
|
-
return { dispose: unsubscribe };
|
|
492
|
-
}
|
|
493
|
-
var ELEMENT_DISPOSABLES = Symbol("hypen.disposables");
|
|
494
|
-
function getElementDisposables(element) {
|
|
495
|
-
const existing = element[ELEMENT_DISPOSABLES];
|
|
496
|
-
if (existing instanceof DisposableStack) {
|
|
497
|
-
return existing;
|
|
498
|
-
}
|
|
499
|
-
const stack = new DisposableStack;
|
|
500
|
-
element[ELEMENT_DISPOSABLES] = stack;
|
|
501
|
-
return stack;
|
|
502
|
-
}
|
|
503
|
-
function disposeElement(element) {
|
|
504
|
-
const stack = element[ELEMENT_DISPOSABLES];
|
|
505
|
-
if (stack instanceof DisposableStack) {
|
|
506
|
-
stack.dispose();
|
|
507
|
-
delete element[ELEMENT_DISPOSABLES];
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
function hasElementDisposables(element) {
|
|
511
|
-
return element[ELEMENT_DISPOSABLES] instanceof DisposableStack;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
class DisposableMixin {
|
|
515
|
-
disposables = new DisposableStack;
|
|
516
|
-
track(disposable) {
|
|
517
|
-
return this.disposables.add(disposable);
|
|
518
|
-
}
|
|
519
|
-
onDispose(callback) {
|
|
520
|
-
this.disposables.addCallback(callback);
|
|
521
|
-
}
|
|
522
|
-
dispose() {
|
|
523
|
-
this.disposables.dispose();
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
function compositeDisposable(...disposables) {
|
|
527
|
-
return {
|
|
528
|
-
dispose: () => {
|
|
529
|
-
for (const d of disposables) {
|
|
190
|
+
this._destroyed = true;
|
|
191
|
+
if (this.helloTimeout) {
|
|
192
|
+
clearTimeout(this.helloTimeout);
|
|
193
|
+
this.helloTimeout = null;
|
|
194
|
+
}
|
|
195
|
+
const currentState = this._moduleInstance?.getState() ?? {};
|
|
196
|
+
if (this._sessionId && this.host.module.handlers.onDisconnect) {
|
|
197
|
+
const session = this.host.sessionManager.getActiveSession(this._sessionId);
|
|
198
|
+
if (session) {
|
|
530
199
|
try {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
200
|
+
await this.host.module.handlers.onDisconnect({
|
|
201
|
+
state: currentState,
|
|
202
|
+
session
|
|
203
|
+
});
|
|
204
|
+
} catch (err) {
|
|
205
|
+
log.error(`onDisconnect threw on session ${this.id}:`, err);
|
|
534
206
|
}
|
|
535
207
|
}
|
|
536
208
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
return fn(r);
|
|
551
|
-
} finally {
|
|
552
|
-
r.dispose();
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// src/retry.ts
|
|
557
|
-
var DEFAULT_OPTIONS = {
|
|
558
|
-
maxAttempts: 3,
|
|
559
|
-
delayMs: 1000,
|
|
560
|
-
backoff: "exponential",
|
|
561
|
-
maxDelayMs: 30000,
|
|
562
|
-
jitter: 0.1
|
|
563
|
-
};
|
|
564
|
-
function calculateDelay(attempt, options) {
|
|
565
|
-
let delay;
|
|
566
|
-
switch (options.backoff) {
|
|
567
|
-
case "exponential":
|
|
568
|
-
delay = options.delayMs * Math.pow(2, attempt - 1);
|
|
569
|
-
break;
|
|
570
|
-
case "linear":
|
|
571
|
-
delay = options.delayMs * attempt;
|
|
572
|
-
break;
|
|
573
|
-
case "none":
|
|
574
|
-
delay = options.delayMs;
|
|
575
|
-
break;
|
|
576
|
-
}
|
|
577
|
-
if (options.jitter > 0) {
|
|
578
|
-
const jitterRange = delay * options.jitter;
|
|
579
|
-
delay += (Math.random() * 2 - 1) * jitterRange;
|
|
580
|
-
}
|
|
581
|
-
return Math.min(delay, options.maxDelayMs);
|
|
582
|
-
}
|
|
583
|
-
function sleep(ms, signal) {
|
|
584
|
-
return new Promise((resolve, reject) => {
|
|
585
|
-
if (signal?.aborted) {
|
|
586
|
-
reject(new Error("Retry aborted"));
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
const timeoutId = setTimeout(resolve, ms);
|
|
590
|
-
signal?.addEventListener("abort", () => {
|
|
591
|
-
clearTimeout(timeoutId);
|
|
592
|
-
reject(new Error("Retry aborted"));
|
|
593
|
-
});
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
async function retry(fn, options = {}) {
|
|
597
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
598
|
-
let lastError = new Error("No attempts made");
|
|
599
|
-
for (let attempt = 1;attempt <= opts.maxAttempts; attempt++) {
|
|
600
|
-
try {
|
|
601
|
-
if (opts.signal?.aborted) {
|
|
602
|
-
throw new Error("Retry aborted");
|
|
603
|
-
}
|
|
604
|
-
return await fn();
|
|
605
|
-
} catch (e) {
|
|
606
|
-
lastError = e instanceof Error ? e : new Error(String(e));
|
|
607
|
-
if (opts.shouldRetry && !opts.shouldRetry(lastError)) {
|
|
608
|
-
throw lastError;
|
|
609
|
-
}
|
|
610
|
-
if (attempt === opts.maxAttempts) {
|
|
611
|
-
break;
|
|
209
|
+
if (this._sessionId) {
|
|
210
|
+
this.host.sessionManager.untrackConnection(this._sessionId, this);
|
|
211
|
+
if (this.host.sessionManager.getConnectionCount(this._sessionId) === 0) {
|
|
212
|
+
const session = this.host.sessionManager.getActiveSession(this._sessionId);
|
|
213
|
+
if (session) {
|
|
214
|
+
this.host.sessionManager.suspendSession(this._sessionId, currentState, async (expiredSession) => {
|
|
215
|
+
if (this.host.module.handlers.onExpire) {
|
|
216
|
+
await this.host.module.handlers.onExpire({
|
|
217
|
+
session: expiredSession
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
612
222
|
}
|
|
613
|
-
const delayMs = calculateDelay(attempt, opts);
|
|
614
|
-
opts.onRetry?.(attempt, lastError, delayMs);
|
|
615
|
-
await sleep(delayMs, opts.signal);
|
|
616
223
|
}
|
|
617
|
-
|
|
618
|
-
throw lastError;
|
|
619
|
-
}
|
|
620
|
-
async function retryResult(fn, options = {}) {
|
|
621
|
-
try {
|
|
622
|
-
const value = await retry(fn, options);
|
|
623
|
-
return Ok(value);
|
|
624
|
-
} catch (e) {
|
|
625
|
-
return Err(e instanceof Error ? e : new Error(String(e)));
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
function withRetry(fn, options = {}) {
|
|
629
|
-
return (...args) => retry(() => fn(...args), options);
|
|
630
|
-
}
|
|
631
|
-
var RetryConditions = {
|
|
632
|
-
networkErrors: (error) => {
|
|
633
|
-
const message = error.message.toLowerCase();
|
|
634
|
-
return message.includes("network") || message.includes("fetch") || message.includes("timeout") || message.includes("econnrefused") || message.includes("econnreset") || message.includes("socket");
|
|
635
|
-
},
|
|
636
|
-
httpRetryable: (error) => {
|
|
637
|
-
const status = error.status;
|
|
638
|
-
if (!status)
|
|
639
|
-
return false;
|
|
640
|
-
return [408, 429, 500, 502, 503, 504].includes(status);
|
|
641
|
-
},
|
|
642
|
-
websocketErrors: (error) => {
|
|
643
|
-
const message = error.message.toLowerCase();
|
|
644
|
-
return message.includes("websocket") || message.includes("connection") || message.includes("close");
|
|
645
|
-
},
|
|
646
|
-
any: (...conditions) => (error) => conditions.some((c) => c(error)),
|
|
647
|
-
all: (...conditions) => (error) => conditions.every((c) => c(error))
|
|
648
|
-
};
|
|
649
|
-
var RetryPresets = {
|
|
650
|
-
aggressive: {
|
|
651
|
-
maxAttempts: 10,
|
|
652
|
-
delayMs: 500,
|
|
653
|
-
backoff: "exponential",
|
|
654
|
-
maxDelayMs: 60000,
|
|
655
|
-
jitter: 0.2
|
|
656
|
-
},
|
|
657
|
-
conservative: {
|
|
658
|
-
maxAttempts: 3,
|
|
659
|
-
delayMs: 2000,
|
|
660
|
-
backoff: "linear",
|
|
661
|
-
maxDelayMs: 1e4,
|
|
662
|
-
jitter: 0.1
|
|
663
|
-
},
|
|
664
|
-
fast: {
|
|
665
|
-
maxAttempts: 5,
|
|
666
|
-
delayMs: 100,
|
|
667
|
-
backoff: "exponential",
|
|
668
|
-
maxDelayMs: 2000,
|
|
669
|
-
jitter: 0
|
|
670
|
-
},
|
|
671
|
-
websocket: {
|
|
672
|
-
maxAttempts: 10,
|
|
673
|
-
delayMs: 1000,
|
|
674
|
-
backoff: "exponential",
|
|
675
|
-
maxDelayMs: 30000,
|
|
676
|
-
jitter: 0.1,
|
|
677
|
-
shouldRetry: RetryConditions.websocketErrors
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
|
|
681
|
-
// src/remote/client.ts
|
|
682
|
-
var log3 = frameworkLoggers.remote;
|
|
683
|
-
|
|
684
|
-
class RemoteEngine {
|
|
685
|
-
ws = null;
|
|
686
|
-
url;
|
|
687
|
-
state = "disconnected";
|
|
688
|
-
options;
|
|
689
|
-
reconnectAttempts = 0;
|
|
690
|
-
disposables = new DisposableStack;
|
|
691
|
-
reconnectDisposable = null;
|
|
692
|
-
currentSessionId = null;
|
|
693
|
-
sessionOptions;
|
|
694
|
-
patchCallbacks = [];
|
|
695
|
-
stateCallbacks = [];
|
|
696
|
-
connectionCallbacks = [];
|
|
697
|
-
disconnectionCallbacks = [];
|
|
698
|
-
errorCallbacks = [];
|
|
699
|
-
sessionEstablishedCallbacks = [];
|
|
700
|
-
sessionExpiredCallbacks = [];
|
|
701
|
-
currentState = null;
|
|
702
|
-
currentRevision = 0;
|
|
703
|
-
moduleName = "";
|
|
704
|
-
navigationDisposable = null;
|
|
705
|
-
lastViewValue = null;
|
|
706
|
-
handlingPopState = false;
|
|
707
|
-
constructor(url, options = {}) {
|
|
708
|
-
this.url = url;
|
|
709
|
-
const navOpt = options.navigation;
|
|
710
|
-
const navigation = navOpt ? {
|
|
711
|
-
backAction: (typeof navOpt === "object" ? navOpt.backAction : undefined) ?? "navigateBack",
|
|
712
|
-
viewStateKey: (typeof navOpt === "object" ? navOpt.viewStateKey : undefined) ?? "currentView"
|
|
713
|
-
} : undefined;
|
|
714
|
-
this.options = {
|
|
715
|
-
autoReconnect: options.autoReconnect ?? true,
|
|
716
|
-
reconnectInterval: options.reconnectInterval ?? 3000,
|
|
717
|
-
maxReconnectAttempts: options.maxReconnectAttempts ?? 10,
|
|
718
|
-
session: options.session,
|
|
719
|
-
navigation
|
|
720
|
-
};
|
|
721
|
-
this.sessionOptions = options.session;
|
|
722
|
-
if (options.session?.id) {
|
|
723
|
-
this.currentSessionId = options.session.id;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
async connect() {
|
|
727
|
-
if (this.state === "connected" || this.state === "connecting") {
|
|
728
|
-
return Ok(undefined);
|
|
729
|
-
}
|
|
730
|
-
this.state = "connecting";
|
|
731
|
-
return new Promise((resolve) => {
|
|
224
|
+
if (this._autoManagedRouter) {
|
|
732
225
|
try {
|
|
733
|
-
this.
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
this.handleMessage(event.data);
|
|
737
|
-
};
|
|
738
|
-
this.disposables.add(disposableListener(this.ws, "message", messageHandler));
|
|
739
|
-
const errorHandler = () => {
|
|
740
|
-
this.state = "error";
|
|
741
|
-
const error = new ConnectionError(this.url, new Error("WebSocket error"));
|
|
742
|
-
this.errorCallbacks.forEach((cb) => cb(error));
|
|
743
|
-
resolve(Err(error));
|
|
744
|
-
};
|
|
745
|
-
this.disposables.add(disposableListener(this.ws, "error", errorHandler));
|
|
746
|
-
const closeHandler = () => {
|
|
747
|
-
this.state = "disconnected";
|
|
748
|
-
this.disconnectionCallbacks.forEach((cb) => cb());
|
|
749
|
-
this.attemptReconnect();
|
|
750
|
-
};
|
|
751
|
-
this.disposables.add(disposableListener(this.ws, "close", closeHandler));
|
|
752
|
-
this.ws.onopen = () => {
|
|
753
|
-
this.state = "connected";
|
|
754
|
-
this.reconnectAttempts = 0;
|
|
755
|
-
if (this.reconnectDisposable) {
|
|
756
|
-
this.reconnectDisposable.dispose();
|
|
757
|
-
this.reconnectDisposable = null;
|
|
758
|
-
}
|
|
759
|
-
this.sendHello();
|
|
760
|
-
this.connectionCallbacks.forEach((cb) => cb());
|
|
761
|
-
resolve(Ok(undefined));
|
|
762
|
-
};
|
|
763
|
-
} catch (e) {
|
|
764
|
-
this.state = "error";
|
|
765
|
-
const error = new ConnectionError(this.url, e);
|
|
766
|
-
resolve(Err(error));
|
|
226
|
+
await this._autoManagedRouter.stop();
|
|
227
|
+
} catch (err) {
|
|
228
|
+
log.error(`Auto-router stop failed on ${this.id}:`, err);
|
|
767
229
|
}
|
|
768
|
-
|
|
769
|
-
}
|
|
770
|
-
sendHello() {
|
|
771
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
772
|
-
return;
|
|
773
|
-
const hello = {
|
|
774
|
-
type: "hello",
|
|
775
|
-
sessionId: this.currentSessionId ?? this.sessionOptions?.id,
|
|
776
|
-
props: this.sessionOptions?.props,
|
|
777
|
-
persistKey: this.sessionOptions?.persistKey
|
|
778
|
-
};
|
|
779
|
-
this.ws.send(JSON.stringify(hello));
|
|
780
|
-
}
|
|
781
|
-
disconnect() {
|
|
782
|
-
if (this.reconnectDisposable) {
|
|
783
|
-
this.reconnectDisposable.dispose();
|
|
784
|
-
this.reconnectDisposable = null;
|
|
230
|
+
this._autoManagedRouter = null;
|
|
785
231
|
}
|
|
786
|
-
if (this.
|
|
787
|
-
|
|
788
|
-
this.
|
|
232
|
+
if (this._moduleInstance) {
|
|
233
|
+
try {
|
|
234
|
+
await this._moduleInstance.destroy();
|
|
235
|
+
} catch (err) {
|
|
236
|
+
log.error(`ModuleInstance.destroy() failed on ${this.id}:`, err);
|
|
789
237
|
}
|
|
790
|
-
this.ws = null;
|
|
791
238
|
}
|
|
792
|
-
this.
|
|
239
|
+
this.host.onSessionDestroyed(this, this.toRemoteClient());
|
|
240
|
+
this._resolveReady();
|
|
241
|
+
this._resolveClosed();
|
|
793
242
|
}
|
|
794
|
-
|
|
795
|
-
this.
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
this.disposables.dispose();
|
|
801
|
-
}
|
|
802
|
-
setupNavigation() {
|
|
803
|
-
const nav = this.options.navigation;
|
|
804
|
-
if (!nav || typeof window === "undefined")
|
|
805
|
-
return;
|
|
806
|
-
if (this.navigationDisposable)
|
|
807
|
-
return;
|
|
808
|
-
this.navigationDisposable = disposableListener(window, "popstate", () => {
|
|
809
|
-
if (this.handlingPopState)
|
|
810
|
-
return;
|
|
811
|
-
log3.debug("Back navigation detected, dispatching:", nav.backAction);
|
|
812
|
-
this.handlingPopState = true;
|
|
813
|
-
this.dispatchAction(nav.backAction);
|
|
243
|
+
setupComponentResolver() {
|
|
244
|
+
this.engine.setComponentResolver((componentName, _contextPath) => {
|
|
245
|
+
const comp = this.host.discoveredComponents.get(componentName);
|
|
246
|
+
if (!comp)
|
|
247
|
+
return null;
|
|
248
|
+
return { source: comp.template, path: componentName };
|
|
814
249
|
});
|
|
815
250
|
}
|
|
816
|
-
|
|
817
|
-
const
|
|
818
|
-
if (
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
}
|
|
828
|
-
if (this.lastViewValue !== null) {
|
|
829
|
-
window.history.pushState({ view: viewValue }, "", "");
|
|
830
|
-
}
|
|
831
|
-
this.lastViewValue = viewValue;
|
|
832
|
-
}
|
|
833
|
-
resolveStateKey(state, key) {
|
|
834
|
-
if (!state || typeof state !== "object")
|
|
835
|
-
return;
|
|
836
|
-
const parts = key.split(".");
|
|
837
|
-
let current = state;
|
|
838
|
-
for (const part of parts) {
|
|
839
|
-
if (current == null || typeof current !== "object")
|
|
840
|
-
return;
|
|
841
|
-
current = current[part];
|
|
251
|
+
registerNestedModules() {
|
|
252
|
+
const primary = this.host.moduleName;
|
|
253
|
+
if (this.host.app) {
|
|
254
|
+
for (const [name, def] of this.host.app.components) {
|
|
255
|
+
if (name === primary)
|
|
256
|
+
continue;
|
|
257
|
+
const stateKeys = def.initialState !== null && typeof def.initialState === "object" ? Object.keys(def.initialState) : [];
|
|
258
|
+
const snapshot = def.initialState !== null && typeof def.initialState === "object" ? structuredClone(def.initialState) : {};
|
|
259
|
+
this.engine.registerModule(name, def.actions ?? [], stateKeys, snapshot);
|
|
260
|
+
log.info(`Registered nested module "${name}" (${def.actions?.length ?? 0} actions, ${stateKeys.length} state keys)`);
|
|
261
|
+
}
|
|
842
262
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
263
|
+
for (const [name, comp] of this.host.discoveredComponents) {
|
|
264
|
+
if (name === primary)
|
|
265
|
+
continue;
|
|
266
|
+
if (!comp.module)
|
|
267
|
+
continue;
|
|
268
|
+
if (this.host.app?.has(name))
|
|
269
|
+
continue;
|
|
270
|
+
const def = comp.module;
|
|
271
|
+
const hasState = def.initialState !== null && typeof def.initialState === "object" && Object.keys(def.initialState).length > 0;
|
|
272
|
+
const hasActions = (def.actions?.length ?? 0) > 0;
|
|
273
|
+
if (!hasState && !hasActions)
|
|
274
|
+
continue;
|
|
275
|
+
const stateKeys = def.initialState !== null && typeof def.initialState === "object" ? Object.keys(def.initialState) : [];
|
|
276
|
+
const snapshot = def.initialState !== null && typeof def.initialState === "object" ? JSON.parse(JSON.stringify(def.initialState)) : {};
|
|
277
|
+
this.engine.registerModule(name, def.actions ?? [], stateKeys, snapshot);
|
|
278
|
+
log.info(`Registered nested module "${name}" from discovery`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async initializeSession(requestedSessionId, props) {
|
|
282
|
+
if (this._helloReceived || this._destroyed)
|
|
848
283
|
return;
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
284
|
+
this._helloReceived = true;
|
|
285
|
+
log.info(`Initializing session for ${this.id} (sessionId: ${requestedSessionId ?? "new"})`);
|
|
286
|
+
if (this.helloTimeout) {
|
|
287
|
+
clearTimeout(this.helloTimeout);
|
|
288
|
+
this.helloTimeout = null;
|
|
289
|
+
}
|
|
290
|
+
let session;
|
|
291
|
+
let isNew = true;
|
|
292
|
+
let isRestored = false;
|
|
293
|
+
let restoredState = null;
|
|
294
|
+
const sm = this.host.sessionManager;
|
|
295
|
+
if (requestedSessionId) {
|
|
296
|
+
const resumed = sm.resumeSession(requestedSessionId);
|
|
297
|
+
if (resumed) {
|
|
298
|
+
session = resumed.session;
|
|
299
|
+
restoredState = resumed.savedState;
|
|
300
|
+
isNew = false;
|
|
301
|
+
isRestored = true;
|
|
302
|
+
} else {
|
|
303
|
+
const activeSession = sm.getActiveSession(requestedSessionId);
|
|
304
|
+
if (activeSession) {
|
|
305
|
+
const allowed = this.resolveConcurrentConnection(activeSession, props);
|
|
306
|
+
if (!allowed)
|
|
307
|
+
return;
|
|
308
|
+
session = activeSession;
|
|
309
|
+
isNew = false;
|
|
310
|
+
} else {
|
|
311
|
+
session = sm.createSession(props);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
session = sm.createSession(props);
|
|
316
|
+
}
|
|
317
|
+
this._sessionId = session.id;
|
|
318
|
+
sm.trackConnection(session.id, this);
|
|
319
|
+
const sessionAck = {
|
|
320
|
+
type: "sessionAck",
|
|
321
|
+
sessionId: session.id,
|
|
322
|
+
isNew,
|
|
323
|
+
isRestored
|
|
855
324
|
};
|
|
856
|
-
this.
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
this.
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
325
|
+
this.send(sessionAck);
|
|
326
|
+
log.info(`Sent sessionAck to ${this.id} (session: ${session.id})`);
|
|
327
|
+
if (!this._moduleInstance) {
|
|
328
|
+
this._moduleInstance = new HypenModuleInstance(this.engine, this.host.module);
|
|
329
|
+
}
|
|
330
|
+
if (isRestored) {
|
|
331
|
+
await this.triggerReconnect(session, restoredState);
|
|
332
|
+
}
|
|
333
|
+
await this._moduleInstance.waitForReady();
|
|
334
|
+
this.registerNestedModules();
|
|
335
|
+
await new Promise((resolve) => queueMicrotask(resolve));
|
|
336
|
+
const initialPatches = [];
|
|
337
|
+
this.engine.setRenderCallback((patches) => {
|
|
338
|
+
initialPatches.push(...patches);
|
|
339
|
+
});
|
|
340
|
+
try {
|
|
341
|
+
this.engine.renderSource(this.host.ui);
|
|
342
|
+
} catch (err) {
|
|
343
|
+
log.error(`Failed to render UI for ${this.id}:`, err);
|
|
344
|
+
this.transport.close(1011, "Render failed");
|
|
869
345
|
return;
|
|
870
346
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
347
|
+
this.setupStreamingRenderCallback();
|
|
348
|
+
const initialMessage = {
|
|
349
|
+
type: "initialTree",
|
|
350
|
+
module: this.host.moduleName,
|
|
351
|
+
state: this._moduleInstance.getState(),
|
|
352
|
+
patches: initialPatches,
|
|
353
|
+
revision: 0
|
|
875
354
|
};
|
|
876
|
-
this.
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
this.
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
onError(callback) {
|
|
895
|
-
this.errorCallbacks.push(callback);
|
|
896
|
-
return this;
|
|
897
|
-
}
|
|
898
|
-
onSessionEstablished(callback) {
|
|
899
|
-
this.sessionEstablishedCallbacks.push(callback);
|
|
900
|
-
return this;
|
|
901
|
-
}
|
|
902
|
-
onSessionExpired(callback) {
|
|
903
|
-
this.sessionExpiredCallbacks.push(callback);
|
|
904
|
-
return this;
|
|
905
|
-
}
|
|
906
|
-
getConnectionState() {
|
|
907
|
-
return this.state;
|
|
908
|
-
}
|
|
909
|
-
getCurrentState() {
|
|
910
|
-
return this.currentState;
|
|
911
|
-
}
|
|
912
|
-
getRevision() {
|
|
913
|
-
return this.currentRevision;
|
|
914
|
-
}
|
|
915
|
-
getSessionId() {
|
|
916
|
-
return this.currentSessionId;
|
|
917
|
-
}
|
|
918
|
-
handleMessage(data) {
|
|
919
|
-
try {
|
|
920
|
-
const message = JSON.parse(data);
|
|
921
|
-
switch (message.type) {
|
|
922
|
-
case "sessionAck":
|
|
923
|
-
this.handleSessionAck(message);
|
|
924
|
-
break;
|
|
925
|
-
case "sessionExpired":
|
|
926
|
-
this.handleSessionExpired(message);
|
|
927
|
-
break;
|
|
928
|
-
case "initialTree":
|
|
929
|
-
this.handleInitialTree(message);
|
|
930
|
-
break;
|
|
931
|
-
case "patch":
|
|
932
|
-
this.handlePatch(message);
|
|
933
|
-
break;
|
|
934
|
-
case "stateUpdate":
|
|
935
|
-
this.currentState = message.state;
|
|
936
|
-
this.trackViewChange(this.currentState);
|
|
937
|
-
this.stateCallbacks.forEach((cb) => cb(this.currentState));
|
|
938
|
-
break;
|
|
355
|
+
this.send(initialMessage);
|
|
356
|
+
log.info(`Sent initialTree to ${this.id} (${initialPatches.length} patches)`);
|
|
357
|
+
this.host.onSessionReady(this, this.toRemoteClient());
|
|
358
|
+
this._resolveReady();
|
|
359
|
+
if (this.autoRouterEnabled) {
|
|
360
|
+
this.autoWireManagedRouter();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
autoWireManagedRouter() {
|
|
364
|
+
const app = this.host.app ?? new HypenApp;
|
|
365
|
+
const discovered = [];
|
|
366
|
+
const runDiscover = (source, label) => {
|
|
367
|
+
try {
|
|
368
|
+
const blocks = this.engine.discoverRouters(source);
|
|
369
|
+
for (const b of blocks)
|
|
370
|
+
discovered.push(b);
|
|
371
|
+
} catch (err) {
|
|
372
|
+
log.error(`Auto-router: discoverRouters failed on ${label}:`, err);
|
|
939
373
|
}
|
|
940
|
-
} catch (e) {
|
|
941
|
-
log3.error("Error handling remote message:", e);
|
|
942
|
-
const error = e instanceof Error ? e : new Error(String(e));
|
|
943
|
-
this.errorCallbacks.forEach((cb) => cb(error));
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
handleSessionAck(message) {
|
|
947
|
-
this.currentSessionId = message.sessionId;
|
|
948
|
-
const info = {
|
|
949
|
-
sessionId: message.sessionId,
|
|
950
|
-
isNew: message.isNew,
|
|
951
|
-
isRestored: message.isRestored
|
|
952
374
|
};
|
|
953
|
-
this.
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
this.sessionExpiredCallbacks.forEach((cb) => cb(message.reason));
|
|
958
|
-
}
|
|
959
|
-
handleInitialTree(message) {
|
|
960
|
-
this.moduleName = message.module;
|
|
961
|
-
this.currentState = message.state;
|
|
962
|
-
this.currentRevision = message.revision;
|
|
963
|
-
if (message.patches.length > 0) {
|
|
964
|
-
this.patchCallbacks.forEach((cb) => cb(message.patches));
|
|
375
|
+
runDiscover(this.host.ui, this.id);
|
|
376
|
+
for (const [name, comp] of this.host.discoveredComponents) {
|
|
377
|
+
if (comp.template)
|
|
378
|
+
runDiscover(comp.template, `${this.id} / ${name}`);
|
|
965
379
|
}
|
|
966
|
-
|
|
967
|
-
this.setupNavigation();
|
|
968
|
-
if (this.options.navigation) {
|
|
969
|
-
this.subscribeState();
|
|
970
|
-
}
|
|
971
|
-
this.stateCallbacks.forEach((cb) => cb(message.state));
|
|
972
|
-
}
|
|
973
|
-
handlePatch(message) {
|
|
974
|
-
if (message.revision <= this.currentRevision) {
|
|
975
|
-
log3.warn(`Out of order patch: expected > ${this.currentRevision}, got ${message.revision}`);
|
|
380
|
+
if (discovered.length === 0)
|
|
976
381
|
return;
|
|
382
|
+
const primaryScope = this.host.moduleName.toLowerCase();
|
|
383
|
+
const router = new HypenRouter;
|
|
384
|
+
const globalContext = new HypenGlobalContext;
|
|
385
|
+
const managed = new ManagedRouter(router, this.engine, app, globalContext);
|
|
386
|
+
const primary = this._moduleInstance;
|
|
387
|
+
if (primary) {
|
|
388
|
+
globalContext.registerModule(primaryScope, primary);
|
|
389
|
+
}
|
|
390
|
+
let added = 0;
|
|
391
|
+
const seenPaths = new Set;
|
|
392
|
+
for (const block of discovered) {
|
|
393
|
+
for (const route of block.routes) {
|
|
394
|
+
if (seenPaths.has(route.path)) {
|
|
395
|
+
log.debug(`Auto-router: path "${route.path}" already registered; ignoring nested duplicate`);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const component = this.pickComponent(route.elementNames);
|
|
399
|
+
if (!component) {
|
|
400
|
+
log.debug(`Auto-router: no registered module matched route "${route.path}" — skipping`);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
managed.addRoute({ path: route.path, component });
|
|
404
|
+
seenPaths.add(route.path);
|
|
405
|
+
added += 1;
|
|
406
|
+
}
|
|
977
407
|
}
|
|
978
|
-
this.
|
|
979
|
-
if (
|
|
980
|
-
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
attemptReconnect() {
|
|
984
|
-
if (!this.options.autoReconnect) {
|
|
408
|
+
const locationKey = this.primaryHasLocationKey() ? "location" : null;
|
|
409
|
+
if (added === 0 && !locationKey) {
|
|
410
|
+
log.debug(`Auto-router: nothing to mount for ${this.id}`);
|
|
985
411
|
return;
|
|
986
412
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
backoff: "exponential",
|
|
998
|
-
maxDelayMs: 30000,
|
|
999
|
-
jitter: 0.1,
|
|
1000
|
-
onRetry: (attempt, error) => {
|
|
1001
|
-
log3.debug(`Reconnection attempt ${attempt}/${this.options.maxReconnectAttempts} failed: ${error.message}`);
|
|
1002
|
-
}
|
|
1003
|
-
}).catch((error) => {
|
|
1004
|
-
log3.error("Max reconnection attempts reached:", error.message);
|
|
1005
|
-
this.errorCallbacks.forEach((cb) => cb(new ConnectionError(this.url, error, this.options.maxReconnectAttempts)));
|
|
413
|
+
if (locationKey) {
|
|
414
|
+
router.onNavigate((rs) => {
|
|
415
|
+
const path = rs.currentPath;
|
|
416
|
+
queueMicrotask(() => {
|
|
417
|
+
try {
|
|
418
|
+
this._moduleInstance?.updateState({ [locationKey]: path });
|
|
419
|
+
} catch (err) {
|
|
420
|
+
log.error(`Auto-router: state.${locationKey} sync failed:`, err);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
1006
423
|
});
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
// src/remote/session.ts
|
|
1012
|
-
class SessionManager {
|
|
1013
|
-
activeSessions = new Map;
|
|
1014
|
-
pendingSessions = new Map;
|
|
1015
|
-
sessionConnections = new Map;
|
|
1016
|
-
config;
|
|
1017
|
-
constructor(config2 = {}) {
|
|
1018
|
-
this.config = {
|
|
1019
|
-
ttl: config2.ttl ?? 3600,
|
|
1020
|
-
concurrent: config2.concurrent ?? "kick-old",
|
|
1021
|
-
generateId: config2.generateId ?? (() => crypto.randomUUID())
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
getTtl() {
|
|
1025
|
-
return this.config.ttl;
|
|
1026
|
-
}
|
|
1027
|
-
getConcurrentPolicy() {
|
|
1028
|
-
return this.config.concurrent;
|
|
424
|
+
}
|
|
425
|
+
managed.start();
|
|
426
|
+
router.push(router.getCurrentPath());
|
|
427
|
+
this._autoManagedRouter = managed;
|
|
1029
428
|
}
|
|
1030
|
-
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
this.activeSessions.set(session.id, session);
|
|
1040
|
-
return session;
|
|
429
|
+
pickComponent(elementNames) {
|
|
430
|
+
const app = this.host.app;
|
|
431
|
+
if (!app)
|
|
432
|
+
return null;
|
|
433
|
+
for (const name of elementNames) {
|
|
434
|
+
if (app.has(name))
|
|
435
|
+
return name;
|
|
436
|
+
}
|
|
437
|
+
return null;
|
|
1041
438
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
439
|
+
primaryHasLocationKey() {
|
|
440
|
+
const state = this._moduleInstance?.getState();
|
|
441
|
+
return state !== null && state !== undefined && typeof state === "object" && "location" in state;
|
|
1044
442
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
443
|
+
setupStreamingRenderCallback() {
|
|
444
|
+
this.engine.setRenderCallback((patches) => {
|
|
445
|
+
if (this._destroyed)
|
|
446
|
+
return;
|
|
447
|
+
this._revision++;
|
|
448
|
+
log.info(`Streaming ${patches.length} patches to ${this.id} (rev ${this._revision})`);
|
|
449
|
+
const patchMessage = {
|
|
450
|
+
type: "patch",
|
|
451
|
+
module: this.host.moduleName,
|
|
452
|
+
patches,
|
|
453
|
+
revision: this._revision
|
|
454
|
+
};
|
|
455
|
+
this.send(patchMessage);
|
|
456
|
+
if (this._stateSubscribed && this._moduleInstance) {
|
|
457
|
+
const stateMessage = {
|
|
458
|
+
type: "stateUpdate",
|
|
459
|
+
module: this.host.moduleName,
|
|
460
|
+
state: this._moduleInstance.getState(),
|
|
461
|
+
revision: this._revision
|
|
462
|
+
};
|
|
463
|
+
this.send(stateMessage);
|
|
464
|
+
}
|
|
465
|
+
if (this._sessionId && this.host.sessionManager.getConcurrentPolicy() === "allow-multiple") {
|
|
466
|
+
for (const peer of this.host.sessionsForId(this._sessionId)) {
|
|
467
|
+
if (peer === this)
|
|
468
|
+
continue;
|
|
469
|
+
peer.send(patchMessage);
|
|
470
|
+
if (peer._stateSubscribed && this._moduleInstance) {
|
|
471
|
+
const stateMessage = {
|
|
472
|
+
type: "stateUpdate",
|
|
473
|
+
module: this.host.moduleName,
|
|
474
|
+
state: this._moduleInstance.getState(),
|
|
475
|
+
revision: this._revision
|
|
476
|
+
};
|
|
477
|
+
peer.send(stateMessage);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
});
|
|
1047
482
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
483
|
+
resolveConcurrentConnection(existingSession, _props) {
|
|
484
|
+
const policy = this.host.sessionManager.getConcurrentPolicy();
|
|
485
|
+
switch (policy) {
|
|
486
|
+
case "kick-old": {
|
|
487
|
+
for (const peer of this.host.sessionsForId(existingSession.id)) {
|
|
488
|
+
if (peer === this)
|
|
489
|
+
continue;
|
|
490
|
+
peer.expireAndClose("kicked");
|
|
491
|
+
}
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
case "reject-new": {
|
|
495
|
+
this.expireAndClose("kicked");
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
case "allow-multiple":
|
|
499
|
+
default:
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
1050
502
|
}
|
|
1051
|
-
|
|
1052
|
-
const
|
|
1053
|
-
if (!
|
|
503
|
+
async triggerReconnect(session, savedState) {
|
|
504
|
+
const handler = this.host.module.handlers.onReconnect;
|
|
505
|
+
if (!handler)
|
|
1054
506
|
return;
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
this.pendingSessions.delete(sessionId);
|
|
1060
|
-
await onExpire(pending.session);
|
|
507
|
+
const restore = (state) => {
|
|
508
|
+
if (state === null || typeof state !== "object") {
|
|
509
|
+
log.warn("restore() called with non-object state, ignoring:", typeof state);
|
|
510
|
+
return;
|
|
1061
511
|
}
|
|
1062
|
-
|
|
1063
|
-
this.pendingSessions.set(sessionId, {
|
|
1064
|
-
session,
|
|
1065
|
-
savedState,
|
|
1066
|
-
expiryTimer
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
resumeSession(sessionId) {
|
|
1070
|
-
const pending = this.pendingSessions.get(sessionId);
|
|
1071
|
-
if (!pending)
|
|
1072
|
-
return null;
|
|
1073
|
-
clearTimeout(pending.expiryTimer);
|
|
1074
|
-
this.pendingSessions.delete(sessionId);
|
|
1075
|
-
pending.session.lastConnectedAt = new Date;
|
|
1076
|
-
this.activeSessions.set(sessionId, pending.session);
|
|
1077
|
-
return {
|
|
1078
|
-
session: pending.session,
|
|
1079
|
-
savedState: pending.savedState
|
|
512
|
+
this._moduleInstance?.updateState(state);
|
|
1080
513
|
};
|
|
514
|
+
await handler({ session, restore });
|
|
1081
515
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
516
|
+
}
|
|
517
|
+
function createBunWebSocketTransport(ws) {
|
|
518
|
+
return {
|
|
519
|
+
send(message) {
|
|
520
|
+
ws.send(JSON.stringify(message));
|
|
521
|
+
},
|
|
522
|
+
close(code, reason) {
|
|
523
|
+
ws.close(code, reason);
|
|
1088
524
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
class AsyncQueueTransport {
|
|
529
|
+
queue = [];
|
|
530
|
+
waiters = [];
|
|
531
|
+
closed = false;
|
|
532
|
+
send(message) {
|
|
533
|
+
if (this.closed)
|
|
534
|
+
return;
|
|
535
|
+
const w = this.waiters.shift();
|
|
536
|
+
if (w) {
|
|
537
|
+
w({ value: message, done: false });
|
|
538
|
+
} else {
|
|
539
|
+
this.queue.push(message);
|
|
1096
540
|
}
|
|
1097
|
-
connections.add(ws);
|
|
1098
541
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
542
|
+
close(_code, _reason) {
|
|
543
|
+
if (this.closed)
|
|
544
|
+
return;
|
|
545
|
+
this.closed = true;
|
|
546
|
+
while (this.waiters.length) {
|
|
547
|
+
const w = this.waiters.shift();
|
|
548
|
+
w({ value: undefined, done: true });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
stream() {
|
|
552
|
+
const self = this;
|
|
553
|
+
const iter = {
|
|
554
|
+
[Symbol.asyncIterator]() {
|
|
555
|
+
return iter;
|
|
556
|
+
},
|
|
557
|
+
async next() {
|
|
558
|
+
if (self.queue.length) {
|
|
559
|
+
return { value: self.queue.shift(), done: false };
|
|
560
|
+
}
|
|
561
|
+
if (self.closed) {
|
|
562
|
+
return { value: undefined, done: true };
|
|
563
|
+
}
|
|
564
|
+
return new Promise((resolve) => {
|
|
565
|
+
self.waiters.push(resolve);
|
|
566
|
+
});
|
|
567
|
+
},
|
|
568
|
+
async return() {
|
|
569
|
+
self.close();
|
|
570
|
+
return { value: undefined, done: true };
|
|
1105
571
|
}
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
getConnections(sessionId) {
|
|
1109
|
-
return this.sessionConnections.get(sessionId);
|
|
1110
|
-
}
|
|
1111
|
-
getConnectionCount(sessionId) {
|
|
1112
|
-
return this.sessionConnections.get(sessionId)?.size ?? 0;
|
|
1113
|
-
}
|
|
1114
|
-
getStats() {
|
|
1115
|
-
let totalConnections = 0;
|
|
1116
|
-
for (const connections of this.sessionConnections.values()) {
|
|
1117
|
-
totalConnections += connections.size;
|
|
1118
|
-
}
|
|
1119
|
-
return {
|
|
1120
|
-
activeSessions: this.activeSessions.size,
|
|
1121
|
-
pendingSessions: this.pendingSessions.size,
|
|
1122
|
-
totalConnections
|
|
1123
572
|
};
|
|
1124
|
-
|
|
1125
|
-
destroy() {
|
|
1126
|
-
for (const pending of this.pendingSessions.values()) {
|
|
1127
|
-
clearTimeout(pending.expiryTimer);
|
|
1128
|
-
}
|
|
1129
|
-
this.activeSessions.clear();
|
|
1130
|
-
this.pendingSessions.clear();
|
|
1131
|
-
this.sessionConnections.clear();
|
|
573
|
+
return iter;
|
|
1132
574
|
}
|
|
1133
575
|
}
|
|
1134
576
|
export {
|
|
577
|
+
createBunWebSocketTransport,
|
|
1135
578
|
SessionManager,
|
|
1136
|
-
|
|
579
|
+
RemoteSession,
|
|
580
|
+
RemoteEngine,
|
|
581
|
+
AsyncQueueTransport
|
|
1137
582
|
};
|
|
1138
583
|
|
|
1139
|
-
//# debugId=
|
|
584
|
+
//# debugId=EC47228D45CD496064756E2164756E21
|