tywrap 0.1.1 → 0.1.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 +23 -3
- package/dist/cli.js +70 -3
- package/dist/cli.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +47 -11
- package/dist/config/index.js.map +1 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +6 -2
- package/dist/core/generator.js.map +1 -1
- package/dist/core/mapper.d.ts.map +1 -1
- package/dist/core/mapper.js +16 -4
- package/dist/core/mapper.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/bridge-core.d.ts +64 -0
- package/dist/runtime/bridge-core.d.ts.map +1 -0
- package/dist/runtime/bridge-core.js +344 -0
- package/dist/runtime/bridge-core.js.map +1 -0
- package/dist/runtime/node.d.ts +5 -6
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +94 -190
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +4 -7
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +117 -194
- package/dist/runtime/optimized-node.js.map +1 -1
- package/dist/runtime/timed-out-request-tracker.d.ts +20 -0
- package/dist/runtime/timed-out-request-tracker.d.ts.map +1 -0
- package/dist/runtime/timed-out-request-tracker.js +48 -0
- package/dist/runtime/timed-out-request-tracker.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +17 -4
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +68 -35
- package/dist/tywrap.js.map +1 -1
- package/dist/utils/codec.d.ts +22 -0
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +245 -55
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +1 -0
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/python.d.ts.map +1 -1
- package/dist/utils/python.js.map +1 -1
- package/package.json +11 -2
- package/runtime/python_bridge.py +450 -69
- package/src/cli.ts +75 -3
- package/src/config/index.ts +58 -15
- package/src/core/generator.ts +6 -2
- package/src/core/mapper.ts +16 -4
- package/src/index.ts +2 -1
- package/src/runtime/bridge-core.ts +454 -0
- package/src/runtime/node.ts +116 -220
- package/src/runtime/optimized-node.ts +160 -243
- package/src/runtime/timed-out-request-tracker.ts +58 -0
- package/src/types/index.ts +3 -0
- package/src/tywrap.ts +91 -36
- package/src/utils/codec.ts +299 -82
- package/src/utils/logger.ts +1 -0
- package/src/utils/python.ts +0 -1
package/src/runtime/node.ts
CHANGED
|
@@ -5,34 +5,25 @@
|
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
6
|
import { delimiter, isAbsolute, join, resolve } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
8
9
|
|
|
9
|
-
import { decodeValueAsync } from '../utils/codec.js';
|
|
10
|
+
import { autoRegisterArrowDecoder, decodeValueAsync } from '../utils/codec.js';
|
|
10
11
|
import { getDefaultPythonPath } from '../utils/python.js';
|
|
11
12
|
import { getVenvBinDir, getVenvPythonExe } from '../utils/runtime.js';
|
|
12
13
|
import type { BridgeInfo } from '../types/index.js';
|
|
13
14
|
|
|
14
15
|
import { RuntimeBridge } from './base.js';
|
|
16
|
+
import { BridgeDisposedError, BridgeProtocolError } from './errors.js';
|
|
15
17
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
protocol: string;
|
|
26
|
-
method: 'call' | 'instantiate' | 'call_method' | 'dispose_instance' | 'meta';
|
|
27
|
-
params: unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface RpcResponse<T = unknown> {
|
|
31
|
-
id: number;
|
|
32
|
-
protocol: string;
|
|
33
|
-
result?: T;
|
|
34
|
-
error?: { type: string; message: string; traceback?: string };
|
|
35
|
-
}
|
|
18
|
+
BridgeCore,
|
|
19
|
+
type RpcRequest,
|
|
20
|
+
ensureJsonFallback,
|
|
21
|
+
ensurePythonEncoding,
|
|
22
|
+
getMaxLineLengthFromEnv,
|
|
23
|
+
getPathKey,
|
|
24
|
+
normalizeEnv,
|
|
25
|
+
validateBridgeInfo,
|
|
26
|
+
} from './bridge-core.js';
|
|
36
27
|
|
|
37
28
|
export interface NodeBridgeOptions {
|
|
38
29
|
pythonPath?: string;
|
|
@@ -40,6 +31,8 @@ export interface NodeBridgeOptions {
|
|
|
40
31
|
virtualEnv?: string;
|
|
41
32
|
cwd?: string;
|
|
42
33
|
timeoutMs?: number;
|
|
34
|
+
maxLineLength?: number;
|
|
35
|
+
inheritProcessEnv?: boolean;
|
|
43
36
|
/**
|
|
44
37
|
* When true, sets TYWRAP_CODEC_FALLBACK=json for the Python process to prefer JSON encoding
|
|
45
38
|
* for rich types (ndarray/dataframe/series). Default: false for fast-fail on Arrow path issues.
|
|
@@ -57,6 +50,8 @@ interface ResolvedNodeBridgeOptions {
|
|
|
57
50
|
virtualEnv?: string;
|
|
58
51
|
cwd: string;
|
|
59
52
|
timeoutMs: number;
|
|
53
|
+
maxLineLength?: number;
|
|
54
|
+
inheritProcessEnv: boolean;
|
|
60
55
|
enableJsonFallback: boolean;
|
|
61
56
|
env: Record<string, string | undefined>;
|
|
62
57
|
}
|
|
@@ -85,15 +80,9 @@ function resolveVirtualEnv(
|
|
|
85
80
|
|
|
86
81
|
export class NodeBridge extends RuntimeBridge {
|
|
87
82
|
private child?: import('child_process').ChildProcess;
|
|
88
|
-
private
|
|
89
|
-
private readonly pending = new Map<
|
|
90
|
-
number,
|
|
91
|
-
{ resolve: (v: unknown) => void; reject: (e: unknown) => void; timer?: NodeJS.Timeout }
|
|
92
|
-
>();
|
|
83
|
+
private core?: BridgeCore;
|
|
93
84
|
private readonly options: ResolvedNodeBridgeOptions;
|
|
94
|
-
private stderrBuffer = '';
|
|
95
85
|
private disposed = false;
|
|
96
|
-
private protocolError = false;
|
|
97
86
|
private initPromise?: Promise<void>;
|
|
98
87
|
private bridgeInfo?: BridgeInfo;
|
|
99
88
|
|
|
@@ -110,6 +99,8 @@ export class NodeBridge extends RuntimeBridge {
|
|
|
110
99
|
virtualEnv,
|
|
111
100
|
cwd,
|
|
112
101
|
timeoutMs: options.timeoutMs ?? 30000,
|
|
102
|
+
maxLineLength: options.maxLineLength,
|
|
103
|
+
inheritProcessEnv: options.inheritProcessEnv ?? false,
|
|
113
104
|
enableJsonFallback: options.enableJsonFallback ?? false,
|
|
114
105
|
env: options.env ?? {},
|
|
115
106
|
};
|
|
@@ -181,111 +172,30 @@ export class NodeBridge extends RuntimeBridge {
|
|
|
181
172
|
|
|
182
173
|
async dispose(): Promise<void> {
|
|
183
174
|
this.disposed = true;
|
|
184
|
-
this.
|
|
185
|
-
this.
|
|
186
|
-
if (!this.child) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
this.child.kill('SIGTERM');
|
|
190
|
-
this.child = undefined;
|
|
175
|
+
this.core?.handleProcessExit();
|
|
176
|
+
this.resetProcess();
|
|
191
177
|
}
|
|
192
178
|
|
|
193
179
|
private async send<T>(payload: Omit<RpcRequest, 'id' | 'protocol'>): Promise<T> {
|
|
194
180
|
if (this.disposed) {
|
|
195
181
|
throw new BridgeDisposedError('Bridge has been disposed');
|
|
196
182
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const text = `${JSON.stringify(message)}\n`;
|
|
200
|
-
let timer: NodeJS.Timeout | undefined;
|
|
201
|
-
const promise = new Promise<T>((resolvePromise, reject) => {
|
|
202
|
-
timer = setTimeout(() => {
|
|
203
|
-
this.pending.delete(id);
|
|
204
|
-
const stderrTail = this.stderrBuffer.trim();
|
|
205
|
-
const msg = stderrTail
|
|
206
|
-
? `Python call timed out. Recent stderr from Python:\n${stderrTail}`
|
|
207
|
-
: 'Python call timed out';
|
|
208
|
-
reject(new BridgeTimeoutError(msg));
|
|
209
|
-
}, this.options.timeoutMs);
|
|
210
|
-
const resolveWrapped = (v: unknown): void => {
|
|
211
|
-
resolvePromise(v as T);
|
|
212
|
-
};
|
|
213
|
-
this.pending.set(id, { resolve: resolveWrapped, reject, timer });
|
|
214
|
-
});
|
|
215
|
-
try {
|
|
216
|
-
if (!this.child?.stdin) {
|
|
217
|
-
throw new BridgeProtocolError('Python process not available');
|
|
218
|
-
}
|
|
219
|
-
this.child.stdin.write(text);
|
|
220
|
-
} catch (err) {
|
|
221
|
-
this.pending.delete(id);
|
|
222
|
-
if (timer) {
|
|
223
|
-
clearTimeout(timer);
|
|
224
|
-
}
|
|
225
|
-
throw new BridgeProtocolError(`IPC failure: ${(err as Error).message}`);
|
|
226
|
-
}
|
|
227
|
-
return promise;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
private errorFrom(err: { type: string; message: string; traceback?: string }): Error {
|
|
231
|
-
const e = new BridgeExecutionError(`${err.type}: ${err.message}`);
|
|
232
|
-
e.traceback = err.traceback;
|
|
233
|
-
return e;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
private handleProtocolError(details: string, line?: string): void {
|
|
237
|
-
if (this.protocolError) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
this.protocolError = true;
|
|
241
|
-
const snippet = line ? (line.length > 500 ? `${line.slice(0, 500)}…` : line) : undefined;
|
|
242
|
-
const hint =
|
|
243
|
-
'Ensure your Python code does not print to stdout and that the bridge outputs only JSON lines.';
|
|
244
|
-
const msg = snippet
|
|
245
|
-
? `Protocol error from Python bridge. ${details}\n${hint}\nOffending line: ${snippet}`
|
|
246
|
-
: `Protocol error from Python bridge. ${details}\n${hint}`;
|
|
247
|
-
const error = new BridgeProtocolError(msg);
|
|
248
|
-
for (const [, p] of this.pending) {
|
|
249
|
-
p.reject(error);
|
|
183
|
+
if (!this.core) {
|
|
184
|
+
throw new BridgeProtocolError('Python process not available');
|
|
250
185
|
}
|
|
251
|
-
this.
|
|
252
|
-
this.child?.kill('SIGTERM');
|
|
253
|
-
this.child = undefined;
|
|
254
|
-
this.initPromise = undefined;
|
|
255
|
-
this.bridgeInfo = undefined;
|
|
186
|
+
return this.core.send<T>(payload);
|
|
256
187
|
}
|
|
257
188
|
|
|
258
189
|
private async startProcess(): Promise<void> {
|
|
259
190
|
try {
|
|
191
|
+
const require = createRequire(import.meta.url);
|
|
192
|
+
await autoRegisterArrowDecoder({
|
|
193
|
+
loader: () => require('apache-arrow'),
|
|
194
|
+
});
|
|
260
195
|
const { spawn } = await import('child_process');
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
for (const [k, v] of Object.entries(process.env)) {
|
|
265
|
-
if (allowedKeys.has(k) || allowedPrefixes.some(p => k.startsWith(p))) {
|
|
266
|
-
baseEnv.set(k, v);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
const env: NodeJS.ProcessEnv = {
|
|
270
|
-
...(Object.fromEntries(baseEnv) as NodeJS.ProcessEnv),
|
|
271
|
-
...this.options.env,
|
|
272
|
-
};
|
|
273
|
-
if (this.options.virtualEnv) {
|
|
274
|
-
const venv = resolveVirtualEnv(this.options.virtualEnv, this.options.cwd);
|
|
275
|
-
env.VIRTUAL_ENV = venv.venvPath;
|
|
276
|
-
const currentPath = env.PATH ?? process.env.PATH ?? '';
|
|
277
|
-
env.PATH = `${venv.binDir}${delimiter}${currentPath}`;
|
|
278
|
-
}
|
|
279
|
-
if (!env.PYTHONUTF8) {
|
|
280
|
-
env.PYTHONUTF8 = '1';
|
|
281
|
-
}
|
|
282
|
-
if (!env.PYTHONIOENCODING) {
|
|
283
|
-
env.PYTHONIOENCODING = 'UTF-8';
|
|
284
|
-
}
|
|
285
|
-
// Respect explicit request for JSON fallback only; otherwise fast-fail by default
|
|
286
|
-
if (this.options.enableJsonFallback && !env.TYWRAP_CODEC_FALLBACK) {
|
|
287
|
-
env.TYWRAP_CODEC_FALLBACK = 'json';
|
|
288
|
-
}
|
|
196
|
+
|
|
197
|
+
const env = this.buildEnv();
|
|
198
|
+
const maxLineLength = this.options.maxLineLength ?? getMaxLineLengthFromEnv(env);
|
|
289
199
|
|
|
290
200
|
let child: ReturnType<typeof spawn>;
|
|
291
201
|
try {
|
|
@@ -307,122 +217,108 @@ export class NodeBridge extends RuntimeBridge {
|
|
|
307
217
|
}
|
|
308
218
|
|
|
309
219
|
this.child = child;
|
|
310
|
-
this.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
220
|
+
this.core = new BridgeCore(
|
|
221
|
+
{
|
|
222
|
+
write: (data: string): void => {
|
|
223
|
+
if (!this.child?.stdin) {
|
|
224
|
+
throw new BridgeProtocolError('Python process not available');
|
|
225
|
+
}
|
|
226
|
+
this.child.stdin.write(data);
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
timeoutMs: this.options.timeoutMs,
|
|
231
|
+
maxLineLength,
|
|
232
|
+
decodeValue: decodeValueAsync,
|
|
233
|
+
onFatalError: (): void => this.resetProcess(),
|
|
316
234
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
this.
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
this.child.stdout?.on('data', chunk => {
|
|
238
|
+
this.core?.handleStdoutData(chunk);
|
|
321
239
|
});
|
|
322
240
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
buffer += chunk.toString();
|
|
326
|
-
let idx: number;
|
|
327
|
-
while ((idx = buffer.indexOf('\n')) !== -1) {
|
|
328
|
-
const line = buffer.slice(0, idx);
|
|
329
|
-
buffer = buffer.slice(idx + 1);
|
|
330
|
-
if (!line.trim()) {
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
(async (): Promise<void> => {
|
|
334
|
-
try {
|
|
335
|
-
const msg = JSON.parse(line) as RpcResponse;
|
|
336
|
-
if (msg.protocol !== TYWRAP_PROTOCOL) {
|
|
337
|
-
this.handleProtocolError(
|
|
338
|
-
`Invalid protocol. Expected ${TYWRAP_PROTOCOL} but received ${String(
|
|
339
|
-
msg.protocol
|
|
340
|
-
)}`,
|
|
341
|
-
line
|
|
342
|
-
);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
if (typeof msg.id !== 'number') {
|
|
346
|
-
this.handleProtocolError('Invalid response id', line);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
const pending = this.pending.get(msg.id);
|
|
350
|
-
if (!pending) {
|
|
351
|
-
this.handleProtocolError(`Unexpected response id ${msg.id}`, line);
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
this.pending.delete(msg.id);
|
|
355
|
-
if (pending.timer) {
|
|
356
|
-
clearTimeout(pending.timer);
|
|
357
|
-
}
|
|
358
|
-
if (msg.error) {
|
|
359
|
-
pending.reject(this.errorFrom(msg.error));
|
|
360
|
-
} else {
|
|
361
|
-
try {
|
|
362
|
-
const decoded = await decodeValueAsync(msg.result);
|
|
363
|
-
pending.resolve(decoded);
|
|
364
|
-
} catch (err) {
|
|
365
|
-
pending.reject(
|
|
366
|
-
new BridgeProtocolError(
|
|
367
|
-
`Failed to decode Python response: ${
|
|
368
|
-
err instanceof Error ? err.message : String(err)
|
|
369
|
-
}`
|
|
370
|
-
)
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
} catch (err) {
|
|
375
|
-
const parseMessage = err instanceof Error ? err.message : String(err);
|
|
376
|
-
this.handleProtocolError(`Invalid JSON: ${parseMessage}`, line);
|
|
377
|
-
}
|
|
378
|
-
})().catch(() => {
|
|
379
|
-
/* ignore */
|
|
380
|
-
});
|
|
381
|
-
}
|
|
241
|
+
this.child.stderr?.on('data', chunk => {
|
|
242
|
+
this.core?.handleStderrData(chunk);
|
|
382
243
|
});
|
|
383
244
|
|
|
384
|
-
this.child
|
|
385
|
-
|
|
386
|
-
try {
|
|
387
|
-
this.stderrBuffer += chunk.toString();
|
|
388
|
-
// Truncate to last 8KB to avoid unbounded growth
|
|
389
|
-
const MAX = 8 * 1024;
|
|
390
|
-
if (this.stderrBuffer.length > MAX) {
|
|
391
|
-
this.stderrBuffer = this.stderrBuffer.slice(this.stderrBuffer.length - MAX);
|
|
392
|
-
}
|
|
393
|
-
} catch {
|
|
394
|
-
// ignore
|
|
395
|
-
}
|
|
245
|
+
this.child.on('error', err => {
|
|
246
|
+
this.core?.handleProcessError(err);
|
|
396
247
|
});
|
|
397
248
|
|
|
398
|
-
this.child
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const msg = stderrTail
|
|
402
|
-
? `Python process exited. Stderr:\n${stderrTail}`
|
|
403
|
-
: 'Python process exited';
|
|
404
|
-
p.reject(new BridgeProtocolError(msg));
|
|
405
|
-
}
|
|
406
|
-
this.pending.clear();
|
|
407
|
-
this.child = undefined;
|
|
408
|
-
this.initPromise = undefined;
|
|
409
|
-
this.bridgeInfo = undefined;
|
|
249
|
+
this.child.on('exit', () => {
|
|
250
|
+
this.core?.handleProcessExit();
|
|
251
|
+
this.resetProcess();
|
|
410
252
|
});
|
|
411
253
|
|
|
412
254
|
await this.refreshBridgeInfo();
|
|
413
255
|
} catch (err) {
|
|
414
|
-
this.
|
|
415
|
-
this.child = undefined;
|
|
416
|
-
this.initPromise = undefined;
|
|
256
|
+
this.resetProcess();
|
|
417
257
|
throw err;
|
|
418
258
|
}
|
|
419
259
|
}
|
|
420
260
|
|
|
261
|
+
private buildEnv(): NodeJS.ProcessEnv {
|
|
262
|
+
const allowedPrefixes = ['TYWRAP_'];
|
|
263
|
+
const allowedKeys = new Set(['path', 'pythonpath', 'virtual_env', 'pythonhome']);
|
|
264
|
+
const baseEnv: Record<string, string | undefined> = {};
|
|
265
|
+
if (this.options.inheritProcessEnv) {
|
|
266
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
267
|
+
// eslint-disable-next-line security/detect-object-injection -- env keys are dynamic by design
|
|
268
|
+
baseEnv[key] = value;
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
272
|
+
if (
|
|
273
|
+
allowedKeys.has(key.toLowerCase()) ||
|
|
274
|
+
allowedPrefixes.some(prefix => key.startsWith(prefix))
|
|
275
|
+
) {
|
|
276
|
+
// eslint-disable-next-line security/detect-object-injection -- env keys are dynamic by design
|
|
277
|
+
baseEnv[key] = value;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let env = normalizeEnv(baseEnv, this.options.env);
|
|
283
|
+
|
|
284
|
+
if (this.options.virtualEnv) {
|
|
285
|
+
const venv = resolveVirtualEnv(this.options.virtualEnv, this.options.cwd);
|
|
286
|
+
env.VIRTUAL_ENV = venv.venvPath;
|
|
287
|
+
const pathKey = getPathKey(env);
|
|
288
|
+
// eslint-disable-next-line security/detect-object-injection -- env keys are dynamic by design
|
|
289
|
+
const currentPath = env[pathKey] ?? '';
|
|
290
|
+
// eslint-disable-next-line security/detect-object-injection -- env keys are dynamic by design
|
|
291
|
+
env[pathKey] = `${venv.binDir}${delimiter}${currentPath}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
ensurePythonEncoding(env);
|
|
295
|
+
// Respect explicit request for JSON fallback only; otherwise fast-fail by default
|
|
296
|
+
ensureJsonFallback(env, this.options.enableJsonFallback);
|
|
297
|
+
|
|
298
|
+
env = normalizeEnv(env, {});
|
|
299
|
+
return env;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private resetProcess(): void {
|
|
303
|
+
this.core?.clear();
|
|
304
|
+
this.core = undefined;
|
|
305
|
+
if (this.child) {
|
|
306
|
+
try {
|
|
307
|
+
if (this.child.exitCode === null) {
|
|
308
|
+
this.child.kill('SIGTERM');
|
|
309
|
+
}
|
|
310
|
+
} catch {
|
|
311
|
+
// ignore
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
this.child = undefined;
|
|
315
|
+
this.initPromise = undefined;
|
|
316
|
+
this.bridgeInfo = undefined;
|
|
317
|
+
}
|
|
318
|
+
|
|
421
319
|
private async refreshBridgeInfo(): Promise<void> {
|
|
422
320
|
const info = await this.send<BridgeInfo>({ method: 'meta', params: {} });
|
|
423
|
-
|
|
424
|
-
throw new BridgeProtocolError('Invalid bridge info payload');
|
|
425
|
-
}
|
|
321
|
+
validateBridgeInfo(info);
|
|
426
322
|
this.bridgeInfo = info;
|
|
427
323
|
}
|
|
428
324
|
}
|