@limrun/api 0.48.4 → 0.49.1
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/CHANGELOG.md +8 -0
- package/build-settings.d.mts.map +1 -1
- package/build-settings.d.ts.map +1 -1
- package/build-settings.js +4 -5
- package/build-settings.js.map +1 -1
- package/build-settings.mjs +4 -5
- package/build-settings.mjs.map +1 -1
- package/client.d.mts +8 -5
- package/client.d.mts.map +1 -1
- package/client.d.ts +8 -5
- package/client.d.ts.map +1 -1
- package/client.js +4 -4
- package/client.js.map +1 -1
- package/client.mjs +2 -2
- package/client.mjs.map +1 -1
- package/destination-tunnel-dialer.d.mts +30 -2
- package/destination-tunnel-dialer.d.mts.map +1 -1
- package/destination-tunnel-dialer.d.ts +30 -2
- package/destination-tunnel-dialer.d.ts.map +1 -1
- package/destination-tunnel-dialer.js +349 -65
- package/destination-tunnel-dialer.js.map +1 -1
- package/destination-tunnel-dialer.mjs +345 -64
- package/destination-tunnel-dialer.mjs.map +1 -1
- package/destination-tunnel-har.d.mts +23 -0
- package/destination-tunnel-har.d.mts.map +1 -0
- package/destination-tunnel-har.d.ts +23 -0
- package/destination-tunnel-har.d.ts.map +1 -0
- package/destination-tunnel-har.js +164 -0
- package/destination-tunnel-har.js.map +1 -0
- package/destination-tunnel-har.mjs +160 -0
- package/destination-tunnel-har.mjs.map +1 -0
- package/destination-tunnel-inspection.d.mts +71 -0
- package/destination-tunnel-inspection.d.mts.map +1 -0
- package/destination-tunnel-inspection.d.ts +71 -0
- package/destination-tunnel-inspection.d.ts.map +1 -0
- package/destination-tunnel-inspection.js +267 -0
- package/destination-tunnel-inspection.js.map +1 -0
- package/destination-tunnel-inspection.mjs +261 -0
- package/destination-tunnel-inspection.mjs.map +1 -0
- package/destination-tunnel.d.mts +119 -14
- package/destination-tunnel.d.mts.map +1 -1
- package/destination-tunnel.d.ts +119 -14
- package/destination-tunnel.d.ts.map +1 -1
- package/destination-tunnel.js +407 -98
- package/destination-tunnel.js.map +1 -1
- package/destination-tunnel.mjs +374 -73
- package/destination-tunnel.mjs.map +1 -1
- package/exec-client.d.mts +9 -0
- package/exec-client.d.mts.map +1 -1
- package/exec-client.d.ts +9 -0
- package/exec-client.d.ts.map +1 -1
- package/exec-client.js.map +1 -1
- package/exec-client.mjs.map +1 -1
- package/index.d.mts +8 -1
- package/index.d.mts.map +1 -1
- package/index.d.ts +8 -1
- package/index.d.ts.map +1 -1
- package/index.js +26 -2
- package/index.js.map +1 -1
- package/index.mjs +7 -1
- package/index.mjs.map +1 -1
- package/instance-client.d.mts +107 -1
- package/instance-client.d.mts.map +1 -1
- package/instance-client.d.ts +107 -1
- package/instance-client.d.ts.map +1 -1
- package/instance-client.js +83 -1
- package/instance-client.js.map +1 -1
- package/instance-client.mjs +83 -1
- package/instance-client.mjs.map +1 -1
- package/internal/destination-tunnel-management.d.mts +4 -3
- package/internal/destination-tunnel-management.d.mts.map +1 -1
- package/internal/destination-tunnel-management.d.ts +4 -3
- package/internal/destination-tunnel-management.d.ts.map +1 -1
- package/internal/destination-tunnel-management.js +64 -46
- package/internal/destination-tunnel-management.js.map +1 -1
- package/internal/destination-tunnel-management.mjs +55 -37
- package/internal/destination-tunnel-management.mjs.map +1 -1
- package/internal/destination-tunnel-url.d.mts +2 -0
- package/internal/destination-tunnel-url.d.mts.map +1 -1
- package/internal/destination-tunnel-url.d.ts +2 -0
- package/internal/destination-tunnel-url.d.ts.map +1 -1
- package/internal/destination-tunnel-url.js +45 -5
- package/internal/destination-tunnel-url.js.map +1 -1
- package/internal/destination-tunnel-url.mjs +44 -5
- package/internal/destination-tunnel-url.mjs.map +1 -1
- package/internal/destination-tunnel-wire-reader.d.mts +23 -0
- package/internal/destination-tunnel-wire-reader.d.mts.map +1 -0
- package/internal/destination-tunnel-wire-reader.d.ts +23 -0
- package/internal/destination-tunnel-wire-reader.d.ts.map +1 -0
- package/internal/destination-tunnel-wire-reader.js +110 -0
- package/internal/destination-tunnel-wire-reader.js.map +1 -0
- package/internal/destination-tunnel-wire-reader.mjs +93 -0
- package/internal/destination-tunnel-wire-reader.mjs.map +1 -0
- package/internal/persist-option.d.mts +14 -0
- package/internal/persist-option.d.mts.map +1 -0
- package/internal/persist-option.d.ts +14 -0
- package/internal/persist-option.d.ts.map +1 -0
- package/internal/persist-option.js +18 -0
- package/internal/persist-option.js.map +1 -0
- package/internal/persist-option.mjs +15 -0
- package/internal/persist-option.mjs.map +1 -0
- package/internal/proxy-transport.d.mts +27 -0
- package/internal/proxy-transport.d.mts.map +1 -1
- package/internal/proxy-transport.d.ts +27 -0
- package/internal/proxy-transport.d.ts.map +1 -1
- package/internal/proxy-transport.js +206 -0
- package/internal/proxy-transport.js.map +1 -1
- package/internal/proxy-transport.mjs +205 -0
- package/internal/proxy-transport.mjs.map +1 -1
- package/internal/session-stream.d.mts +39 -0
- package/internal/session-stream.d.mts.map +1 -0
- package/internal/session-stream.d.ts +39 -0
- package/internal/session-stream.d.ts.map +1 -0
- package/internal/session-stream.js +144 -0
- package/internal/session-stream.js.map +1 -0
- package/internal/session-stream.mjs +139 -0
- package/internal/session-stream.mjs.map +1 -0
- package/internal/tslib.js +4 -4
- package/ios-client.d.mts +63 -4
- package/ios-client.d.mts.map +1 -1
- package/ios-client.d.ts +63 -4
- package/ios-client.d.ts.map +1 -1
- package/ios-client.js +48 -2
- package/ios-client.js.map +1 -1
- package/ios-client.mjs +48 -2
- package/ios-client.mjs.map +1 -1
- package/package.json +22 -1
- package/resources/android-instances-helpers.d.mts +25 -0
- package/resources/android-instances-helpers.d.mts.map +1 -0
- package/resources/android-instances-helpers.d.ts +25 -0
- package/resources/android-instances-helpers.d.ts.map +1 -0
- package/resources/android-instances-helpers.js +37 -0
- package/resources/android-instances-helpers.js.map +1 -0
- package/resources/android-instances-helpers.mjs +33 -0
- package/resources/android-instances-helpers.mjs.map +1 -0
- package/resources/index.d.mts +6 -3
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +6 -3
- package/resources/index.d.ts.map +1 -1
- package/resources/index.js +6 -5
- package/resources/index.js.map +1 -1
- package/resources/index.mjs +3 -3
- package/resources/index.mjs.map +1 -1
- package/resources/ios-instances-helpers.d.mts +25 -0
- package/resources/ios-instances-helpers.d.mts.map +1 -0
- package/resources/ios-instances-helpers.d.ts +25 -0
- package/resources/ios-instances-helpers.d.ts.map +1 -0
- package/resources/ios-instances-helpers.js +37 -0
- package/resources/ios-instances-helpers.js.map +1 -0
- package/resources/ios-instances-helpers.mjs +33 -0
- package/resources/ios-instances-helpers.mjs.map +1 -0
- package/resources/session-artifacts.d.mts +70 -0
- package/resources/session-artifacts.d.mts.map +1 -0
- package/resources/session-artifacts.d.ts +70 -0
- package/resources/session-artifacts.d.ts.map +1 -0
- package/resources/session-artifacts.js +8 -0
- package/resources/session-artifacts.js.map +1 -0
- package/resources/session-artifacts.mjs +7 -0
- package/resources/session-artifacts.mjs.map +1 -0
- package/resources/xcode-instances-helpers.d.mts +55 -0
- package/resources/xcode-instances-helpers.d.mts.map +1 -1
- package/resources/xcode-instances-helpers.d.ts +55 -0
- package/resources/xcode-instances-helpers.d.ts.map +1 -1
- package/resources/xcode-instances-helpers.js +49 -3
- package/resources/xcode-instances-helpers.js.map +1 -1
- package/resources/xcode-instances-helpers.mjs +47 -2
- package/resources/xcode-instances-helpers.mjs.map +1 -1
- package/src/build-settings.ts +4 -5
- package/src/client.ts +12 -2
- package/src/destination-tunnel-dialer.ts +403 -64
- package/src/destination-tunnel-har.ts +200 -0
- package/src/destination-tunnel-inspection.ts +367 -0
- package/src/destination-tunnel.ts +510 -88
- package/src/exec-client.ts +9 -0
- package/src/index.ts +58 -0
- package/src/instance-client.ts +273 -5
- package/src/internal/destination-tunnel-management.ts +67 -43
- package/src/internal/destination-tunnel-url.ts +61 -5
- package/src/internal/destination-tunnel-wire-reader.ts +109 -0
- package/src/internal/persist-option.ts +26 -0
- package/src/internal/proxy-transport.ts +248 -0
- package/src/internal/session-stream.ts +157 -0
- package/src/ios-client.ts +129 -8
- package/src/resources/android-instances-helpers.ts +45 -0
- package/src/resources/index.ts +14 -2
- package/src/resources/ios-instances-helpers.ts +45 -0
- package/src/resources/session-artifacts.ts +95 -0
- package/src/resources/xcode-instances-helpers.ts +116 -5
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
package/src/exec-client.ts
CHANGED
|
@@ -146,6 +146,15 @@ export type WebhookConfig = {
|
|
|
146
146
|
* Transfer-Encoding, Connection) are rejected.
|
|
147
147
|
*/
|
|
148
148
|
headers?: Record<string, string>;
|
|
149
|
+
/**
|
|
150
|
+
* Caller-defined key/value pairs echoed verbatim in the payload's `labels`
|
|
151
|
+
* field, so the receiver can correlate the callback with its own context
|
|
152
|
+
* (a pipeline run, a commit, a ticket) without keeping a lookup table
|
|
153
|
+
* keyed by execId. At most 32 entries; keys and values are printable
|
|
154
|
+
* ASCII of at most 64 characters each, and keys must not be empty. Older
|
|
155
|
+
* build daemons ignore it.
|
|
156
|
+
*/
|
|
157
|
+
labels?: Record<string, string>;
|
|
149
158
|
};
|
|
150
159
|
|
|
151
160
|
/** Android ABIs the gradle daemon accepts; 'all' keeps the project's own configuration. */
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,50 @@ export { Limrun, type ClientOptions } from './client';
|
|
|
8
8
|
export { PagePromise } from './core/pagination';
|
|
9
9
|
export * from './instance-client';
|
|
10
10
|
export * as Ios from './ios-client';
|
|
11
|
+
export {
|
|
12
|
+
isTerminalDestinationTunnelError,
|
|
13
|
+
type DestinationTcpTunnel,
|
|
14
|
+
type DestinationTcpTunnelOptions,
|
|
15
|
+
} from './destination-tunnel-dialer';
|
|
16
|
+
export {
|
|
17
|
+
decodeDestinationTunnelInspectionSSEEvent,
|
|
18
|
+
deriveDestinationTunnelInspectionURL,
|
|
19
|
+
startDestinationTunnelInspectionStream,
|
|
20
|
+
type DestinationTunnelInspectionBodyEvent,
|
|
21
|
+
type DestinationTunnelInspectionComplete,
|
|
22
|
+
type DestinationTunnelInspectionErrorCallback,
|
|
23
|
+
type DestinationTunnelInspectionEvent,
|
|
24
|
+
type DestinationTunnelInspectionEventCallback,
|
|
25
|
+
type DestinationTunnelInspectionExtension,
|
|
26
|
+
type DestinationTunnelInspectionGap,
|
|
27
|
+
type DestinationTunnelInspectionMetadataEvent,
|
|
28
|
+
type DestinationTunnelInspectionStream,
|
|
29
|
+
type DestinationTunnelInspectionStreamOptions,
|
|
30
|
+
} from './destination-tunnel-inspection';
|
|
31
|
+
export {
|
|
32
|
+
DestinationTunnelHARAssembler,
|
|
33
|
+
type DestinationTunnelHAREntry,
|
|
34
|
+
type DestinationTunnelHARExtension,
|
|
35
|
+
} from './destination-tunnel-har';
|
|
36
|
+
export {
|
|
37
|
+
validateDestinationTunnelSelectors,
|
|
38
|
+
normalizeDestinationTunnelInspection,
|
|
39
|
+
disabledDestinationTunnelInspection,
|
|
40
|
+
destinationTunnelConfigHash,
|
|
41
|
+
DestinationTunnelSelectorError,
|
|
42
|
+
DESTINATION_TUNNEL_DEFAULT_MAX_BODY_BYTES,
|
|
43
|
+
DESTINATION_TUNNEL_DEFAULT_TTL_SECONDS,
|
|
44
|
+
DESTINATION_TUNNEL_DEFAULT_WINDOW,
|
|
45
|
+
DESTINATION_TUNNEL_MAX_BODY_BYTES,
|
|
46
|
+
DESTINATION_TUNNEL_MAX_TTL_SECONDS,
|
|
47
|
+
type DestinationTunnelInspectionConfig,
|
|
48
|
+
type DestinationTunnelTransportRequest,
|
|
49
|
+
type DestinationTunnelTransportResult,
|
|
50
|
+
type DestinationTunnelSelectors,
|
|
51
|
+
type DestinationTunnelSelectorReport,
|
|
52
|
+
type DestinationTunnelSelectorErrorCode,
|
|
53
|
+
type DestinationTunnelBindReport,
|
|
54
|
+
} from './destination-tunnel';
|
|
11
55
|
export {
|
|
12
56
|
startHttpProxy,
|
|
13
57
|
startForwardHttpProxy,
|
|
@@ -59,8 +103,13 @@ export {
|
|
|
59
103
|
type RbeBuildSummary,
|
|
60
104
|
type Tunnel,
|
|
61
105
|
RbeUnsupportedError,
|
|
106
|
+
XcodeSelectionUnsupportedError,
|
|
62
107
|
deriveRbeTunnelUrl,
|
|
63
108
|
DEFAULT_RBE_TUNNEL_PORT,
|
|
109
|
+
type XcodeInfo,
|
|
110
|
+
type XcodeStatus,
|
|
111
|
+
type XcodeSelectResult,
|
|
112
|
+
type SandboxInfo,
|
|
64
113
|
type XcodeProjectConfig,
|
|
65
114
|
type XcodeBuildOptions,
|
|
66
115
|
type XcodeRunOptions,
|
|
@@ -87,6 +136,14 @@ export {
|
|
|
87
136
|
type GradleBuildLog,
|
|
88
137
|
} from './resources/gradle-instances-helpers';
|
|
89
138
|
export { type AssetUploadOptions } from './resources/daemon-client-shared';
|
|
139
|
+
export {
|
|
140
|
+
type SessionArtifact,
|
|
141
|
+
type SessionArtifactKind,
|
|
142
|
+
type SessionLogLine,
|
|
143
|
+
type SessionEvent,
|
|
144
|
+
type SessionEventType,
|
|
145
|
+
} from './resources/session-artifacts';
|
|
146
|
+
export { SessionStreamLostError } from './internal/session-stream';
|
|
90
147
|
export {
|
|
91
148
|
LIMRUN_DIR,
|
|
92
149
|
TRY_IMPORT_LINE,
|
|
@@ -143,3 +200,4 @@ export {
|
|
|
143
200
|
PermissionDeniedError,
|
|
144
201
|
UnprocessableEntityError,
|
|
145
202
|
} from './core/error';
|
|
203
|
+
export { isDirectInstanceHttpError, type DirectInstanceHttpError } from './internal/direct-instance-errors';
|
package/src/instance-client.ts
CHANGED
|
@@ -11,10 +11,45 @@ import { nodeProxyTransport } from './internal/proxy-transport';
|
|
|
11
11
|
import { startTcpTunnel, isNonRetryableError } from './tunnel';
|
|
12
12
|
import type { Tunnel } from './tunnel';
|
|
13
13
|
import { syncFolder, type FolderSyncOptions, type SyncFolderResult } from './folder-sync';
|
|
14
|
+
import { startDestinationTcpTunnel, type DestinationTcpTunnel } from './destination-tunnel-dialer';
|
|
15
|
+
import type { DestinationTunnelInspectionConfig, DestinationTunnelSelectors } from './destination-tunnel';
|
|
16
|
+
import type {
|
|
17
|
+
DestinationTunnelInspectionErrorCallback,
|
|
18
|
+
DestinationTunnelInspectionEventCallback,
|
|
19
|
+
} from './destination-tunnel-inspection';
|
|
20
|
+
import {
|
|
21
|
+
getDestinationTunnelStatus,
|
|
22
|
+
stopDestinationTunnel,
|
|
23
|
+
type DestinationTunnelStatus,
|
|
24
|
+
} from './internal/destination-tunnel-management';
|
|
25
|
+
import { deriveDestinationTunnelURL } from './internal/destination-tunnel-url';
|
|
26
|
+
import { persistFields, type PersistOption } from './internal/persist-option';
|
|
27
|
+
import { streamSessionEntries } from './internal/session-stream';
|
|
28
|
+
import type { SessionLogLine, SessionEvent } from './resources/session-artifacts';
|
|
29
|
+
|
|
30
|
+
export { type PersistOption } from './internal/persist-option';
|
|
14
31
|
|
|
15
32
|
const ANDROID_RECORDING_PATH = '/data/local/tmp/recordings/video_recording.mp4';
|
|
16
33
|
const ANDROID_SIGNALING_PATH = '/ws';
|
|
17
34
|
|
|
35
|
+
/** Transparent destination tunnel from the Android instance to this machine. */
|
|
36
|
+
export type DestinationTunnel = DestinationTcpTunnel;
|
|
37
|
+
export type DestinationTunnelOptions = {
|
|
38
|
+
/** Exact endpoint and domain selector values interpreted by the Android tunnel server. */
|
|
39
|
+
selectors: DestinationTunnelSelectors;
|
|
40
|
+
/** Inspection settings negotiated with the Android tunnel server. */
|
|
41
|
+
inspection?: Partial<DestinationTunnelInspectionConfig>;
|
|
42
|
+
/** Called for each validated inspection metadata or body event. */
|
|
43
|
+
onInspectionEvent?: DestinationTunnelInspectionEventCallback;
|
|
44
|
+
/** Called when the independent inspection stream fails or reconnects. */
|
|
45
|
+
onInspectionError?: DestinationTunnelInspectionErrorCallback;
|
|
46
|
+
/** Per-flow receive window in bytes. Defaults to 1 MiB. */
|
|
47
|
+
window?: number;
|
|
48
|
+
/** Controls tunnel logging verbosity. Defaults to the instance client's log level. */
|
|
49
|
+
logLevel?: LogLevel;
|
|
50
|
+
};
|
|
51
|
+
export type { DestinationTunnelStatus } from './internal/destination-tunnel-management';
|
|
52
|
+
|
|
18
53
|
/**
|
|
19
54
|
* Connection state of the instance client
|
|
20
55
|
*/
|
|
@@ -134,6 +169,12 @@ export type InstanceClient = {
|
|
|
134
169
|
* @param packageName Package name of the app to terminate
|
|
135
170
|
*/
|
|
136
171
|
terminateApp: (packageName: string) => Promise<void>;
|
|
172
|
+
/**
|
|
173
|
+
* List the launchable apps installed on the instance, each with its package
|
|
174
|
+
* name (as `bundleId`), display name, install type (`user` or `system`),
|
|
175
|
+
* and, when available, a small base64 PNG icon.
|
|
176
|
+
*/
|
|
177
|
+
listApps: () => Promise<InstalledApp[]>;
|
|
137
178
|
/**
|
|
138
179
|
* Watch an app's exit/crash without launching it through {@link launchApp},
|
|
139
180
|
* e.g. for apps that are already running or will be opened via a deeplink
|
|
@@ -246,8 +287,11 @@ export type InstanceClient = {
|
|
|
246
287
|
* Start recording device video. Use stopRecording() to finish the recording.
|
|
247
288
|
* When provided, `quality` must be one of `5`, `6`, `7`, `8`, `9`, or `10`.
|
|
248
289
|
* The server default is `5`.
|
|
290
|
+
* With `persist`, the completed recording is uploaded to Limrun's bucket
|
|
291
|
+
* when the recording stops or the instance terminates; list it with
|
|
292
|
+
* `androidInstances.listRecordings`.
|
|
249
293
|
*/
|
|
250
|
-
startRecording: (options?: { quality?: RecordingQuality }) => Promise<void>;
|
|
294
|
+
startRecording: (options?: { quality?: RecordingQuality; persist?: PersistOption }) => Promise<void>;
|
|
251
295
|
/**
|
|
252
296
|
* Stop the active server-side recording.
|
|
253
297
|
* If `saveTo.presignedUrl` is provided, the server uploads the completed file there before resolving.
|
|
@@ -255,6 +299,48 @@ export type InstanceClient = {
|
|
|
255
299
|
* Returns a download URL for the completed recording.
|
|
256
300
|
*/
|
|
257
301
|
stopRecording: (saveTo: { presignedUrl?: string; localPath?: string }) => Promise<string>;
|
|
302
|
+
/**
|
|
303
|
+
* Start capturing an app's logcat output (one JSONL object per line) for
|
|
304
|
+
* the given package name. One app log capture runs at a time; the server
|
|
305
|
+
* rejects a second start. With `persist`, the capture is uploaded to
|
|
306
|
+
* Limrun's bucket when it stops or the instance terminates; list it with
|
|
307
|
+
* `androidInstances.listAppLogs`.
|
|
308
|
+
*/
|
|
309
|
+
startAppLogCapture: (options: { bundleId: string; persist?: PersistOption }) => Promise<void>;
|
|
310
|
+
/** Stop the active app log capture. */
|
|
311
|
+
stopAppLogCapture: () => Promise<void>;
|
|
312
|
+
/**
|
|
313
|
+
* Start capturing the coalesced event log of user and agent actions (one
|
|
314
|
+
* JSONL object per line). One event capture runs at a time; the server
|
|
315
|
+
* rejects a second start. With `persist`, the capture is uploaded to
|
|
316
|
+
* Limrun's bucket when it stops or the instance terminates; list it with
|
|
317
|
+
* `androidInstances.listEvents`.
|
|
318
|
+
*/
|
|
319
|
+
startEventCapture: (options?: { persist?: PersistOption }) => Promise<void>;
|
|
320
|
+
/** Stop the active event capture. */
|
|
321
|
+
stopEventCapture: () => Promise<void>;
|
|
322
|
+
/**
|
|
323
|
+
* Tail the live app log stream fed by {@link startAppLogCapture}. On
|
|
324
|
+
* connect the instance replays its recent buffer (up to 1000 lines), then
|
|
325
|
+
* delivers lines as they are captured; a dropped connection reconnects
|
|
326
|
+
* and resumes exactly where it left off, so every line is delivered once.
|
|
327
|
+
* The stream is independent of this client's websocket and survives
|
|
328
|
+
* {@link disconnect}; `onError` fires at most once, after the stream has
|
|
329
|
+
* kept failing for several minutes (e.g. the instance was deleted), and
|
|
330
|
+
* the stream is closed. Returns a function that closes the stream.
|
|
331
|
+
*/
|
|
332
|
+
streamAppLogCapture: (handlers: {
|
|
333
|
+
onLine: (line: SessionLogLine) => void;
|
|
334
|
+
onError?: (error: Error) => void;
|
|
335
|
+
}) => () => void;
|
|
336
|
+
/**
|
|
337
|
+
* Tail the live coalesced event log fed by {@link startEventCapture}.
|
|
338
|
+
* Same delivery and lifecycle semantics as {@link streamAppLogCapture}.
|
|
339
|
+
*/
|
|
340
|
+
streamEventCapture: (handlers: {
|
|
341
|
+
onEvent: (event: SessionEvent) => void;
|
|
342
|
+
onError?: (error: Error) => void;
|
|
343
|
+
}) => () => void;
|
|
258
344
|
/** Send an application-level keepAlive message on the control websocket. */
|
|
259
345
|
keepAlive: () => void;
|
|
260
346
|
/**
|
|
@@ -267,6 +353,22 @@ export type InstanceClient = {
|
|
|
267
353
|
* Returns the local TCP port and a cleanup function.
|
|
268
354
|
*/
|
|
269
355
|
startAdbTunnel: () => Promise<Tunnel>;
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Transparently route declared Android TCP destinations through this
|
|
359
|
+
* machine. Exact `localhost` routes are also reachable on-device as
|
|
360
|
+
* `10.0.2.2:<port>`, following the emulator convention.
|
|
361
|
+
*
|
|
362
|
+
* The caller owns the returned tunnel and must close it. Disconnecting this
|
|
363
|
+
* instance client does not close the tunnel.
|
|
364
|
+
*/
|
|
365
|
+
startTunnel: (options: DestinationTunnelOptions) => Promise<DestinationTunnel>;
|
|
366
|
+
|
|
367
|
+
/** Get the active destination tunnel and most recent terminal failure. */
|
|
368
|
+
getTunnelStatus: () => Promise<DestinationTunnelStatus>;
|
|
369
|
+
|
|
370
|
+
/** Stop the active destination tunnel only when its ID matches `tunnelId`. */
|
|
371
|
+
stopTunnel: (tunnelId: string) => Promise<void>;
|
|
270
372
|
/**
|
|
271
373
|
* Send an asset URL to the instance. The instance will download the asset
|
|
272
374
|
* and process it (currently APK install is supported). Resolves on success,
|
|
@@ -517,6 +619,20 @@ export type LaunchAppResult = {
|
|
|
517
619
|
packageName: string;
|
|
518
620
|
};
|
|
519
621
|
|
|
622
|
+
/**
|
|
623
|
+
* One entry of {@link InstanceClient.listApps}. The package name is carried
|
|
624
|
+
* as `bundleId` so the shape matches iOS's listApps.
|
|
625
|
+
*/
|
|
626
|
+
export type InstalledApp = {
|
|
627
|
+
bundleId: string;
|
|
628
|
+
name: string;
|
|
629
|
+
installType: string;
|
|
630
|
+
/** App icon as a base64-encoded 64px PNG; absent when the runtime could not extract one. */
|
|
631
|
+
icon?: string;
|
|
632
|
+
/** Install time as epoch milliseconds; absent when the runtime cannot tell. */
|
|
633
|
+
installedAt?: number;
|
|
634
|
+
};
|
|
635
|
+
|
|
520
636
|
/** Handle for an app watch registered via {@link InstanceClient.watchApp}. */
|
|
521
637
|
export type AppWatch = {
|
|
522
638
|
/** Identifier of this watch on the server. */
|
|
@@ -703,6 +819,13 @@ type WatchAppResultMessage = {
|
|
|
703
819
|
error?: CommandError;
|
|
704
820
|
};
|
|
705
821
|
|
|
822
|
+
type ListAppsResultMessage = {
|
|
823
|
+
type: 'listAppsResult';
|
|
824
|
+
id: string;
|
|
825
|
+
payload?: { apps?: InstalledApp[] };
|
|
826
|
+
error?: CommandError;
|
|
827
|
+
};
|
|
828
|
+
|
|
706
829
|
type UnwatchAppResultMessage = {
|
|
707
830
|
type: 'unwatchAppResult';
|
|
708
831
|
id: string;
|
|
@@ -770,6 +893,34 @@ type StopVideoRecordingResultMessage = {
|
|
|
770
893
|
error?: CommandError;
|
|
771
894
|
};
|
|
772
895
|
|
|
896
|
+
type StartAppLogCaptureResultMessage = {
|
|
897
|
+
type: 'startAppLogCaptureResult';
|
|
898
|
+
id: string;
|
|
899
|
+
payload?: EmptyCommandResult;
|
|
900
|
+
error?: CommandError;
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
type StopAppLogCaptureResultMessage = {
|
|
904
|
+
type: 'stopAppLogCaptureResult';
|
|
905
|
+
id: string;
|
|
906
|
+
payload?: EmptyCommandResult;
|
|
907
|
+
error?: CommandError;
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
type StartEventCaptureResultMessage = {
|
|
911
|
+
type: 'startEventCaptureResult';
|
|
912
|
+
id: string;
|
|
913
|
+
payload?: EmptyCommandResult;
|
|
914
|
+
error?: CommandError;
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
type StopEventCaptureResultMessage = {
|
|
918
|
+
type: 'stopEventCaptureResult';
|
|
919
|
+
id: string;
|
|
920
|
+
payload?: EmptyCommandResult;
|
|
921
|
+
error?: CommandError;
|
|
922
|
+
};
|
|
923
|
+
|
|
773
924
|
type KnownCommandResultMessage =
|
|
774
925
|
| ScreenshotResultMessage
|
|
775
926
|
| GetElementTreeResultMessage
|
|
@@ -784,12 +935,17 @@ type KnownCommandResultMessage =
|
|
|
784
935
|
| TerminateAppResultMessage
|
|
785
936
|
| WatchAppResultMessage
|
|
786
937
|
| UnwatchAppResultMessage
|
|
938
|
+
| ListAppsResultMessage
|
|
787
939
|
| PlayOnMicrophoneResultMessage
|
|
788
940
|
| CameraControlResultMessage
|
|
789
941
|
| AdbShellResultMessage
|
|
790
942
|
| SetWifiBandwidthResultMessage
|
|
791
943
|
| StartVideoRecordingResultMessage
|
|
792
|
-
| StopVideoRecordingResultMessage
|
|
944
|
+
| StopVideoRecordingResultMessage
|
|
945
|
+
| StartAppLogCaptureResultMessage
|
|
946
|
+
| StopAppLogCaptureResultMessage
|
|
947
|
+
| StartEventCaptureResultMessage
|
|
948
|
+
| StopEventCaptureResultMessage;
|
|
793
949
|
|
|
794
950
|
type ServerMessage =
|
|
795
951
|
| ScreenshotResponse
|
|
@@ -812,12 +968,17 @@ type CommandRequestMap = {
|
|
|
812
968
|
terminateApp: { packageName: string };
|
|
813
969
|
watchApp: { packageName: string; execId: string };
|
|
814
970
|
unwatchApp: { execId: string };
|
|
971
|
+
listApps: {};
|
|
815
972
|
playOnMicrophone: { path: string; once?: boolean };
|
|
816
973
|
cameraControl: { action: 'setSource'; source: 'video'; arg: string; loop?: boolean } | { action: 'reset' };
|
|
817
974
|
adbShell: { command: string; timeoutMs?: number };
|
|
818
975
|
setWifiBandwidth: WifiBandwidthOptions;
|
|
819
|
-
startRecording: { quality?: RecordingQuality };
|
|
976
|
+
startRecording: { quality?: RecordingQuality; persist?: boolean; ttlSeconds?: number };
|
|
820
977
|
stopRecording: { upload?: { presignedUrl: string } };
|
|
978
|
+
startAppLogCapture: { bundleId: string; persist?: boolean; ttlSeconds?: number };
|
|
979
|
+
stopAppLogCapture: {};
|
|
980
|
+
startEventCapture: { persist?: boolean; ttlSeconds?: number };
|
|
981
|
+
stopEventCapture: {};
|
|
821
982
|
};
|
|
822
983
|
|
|
823
984
|
type CommandResultMap = {
|
|
@@ -834,12 +995,17 @@ type CommandResultMap = {
|
|
|
834
995
|
terminateApp: { packageName?: string };
|
|
835
996
|
watchApp: { packageName?: string };
|
|
836
997
|
unwatchApp: EmptyCommandResult;
|
|
998
|
+
listApps: { apps?: InstalledApp[] };
|
|
837
999
|
playOnMicrophone: PlayOnMicrophoneResult;
|
|
838
1000
|
cameraControl: EmptyCommandResult;
|
|
839
1001
|
adbShell: AdbShellResultMessage;
|
|
840
1002
|
setWifiBandwidth: EmptyCommandResult;
|
|
841
1003
|
startRecording: EmptyCommandResult;
|
|
842
1004
|
stopRecording: EmptyCommandResult;
|
|
1005
|
+
startAppLogCapture: EmptyCommandResult;
|
|
1006
|
+
stopAppLogCapture: EmptyCommandResult;
|
|
1007
|
+
startEventCapture: EmptyCommandResult;
|
|
1008
|
+
stopEventCapture: EmptyCommandResult;
|
|
843
1009
|
};
|
|
844
1010
|
|
|
845
1011
|
type PendingRequest<T> = {
|
|
@@ -1015,12 +1181,17 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1015
1181
|
case 'terminateAppResult':
|
|
1016
1182
|
case 'watchAppResult':
|
|
1017
1183
|
case 'unwatchAppResult':
|
|
1184
|
+
case 'listAppsResult':
|
|
1018
1185
|
case 'playOnMicrophoneResult':
|
|
1019
1186
|
case 'cameraControlResult':
|
|
1020
1187
|
case 'adbShellResult':
|
|
1021
1188
|
case 'setWifiBandwidthResult':
|
|
1022
1189
|
case 'startRecordingResult':
|
|
1023
1190
|
case 'stopRecordingResult':
|
|
1191
|
+
case 'startAppLogCaptureResult':
|
|
1192
|
+
case 'stopAppLogCaptureResult':
|
|
1193
|
+
case 'startEventCaptureResult':
|
|
1194
|
+
case 'stopEventCaptureResult':
|
|
1024
1195
|
return 'id' in message && typeof message.id === 'string';
|
|
1025
1196
|
default:
|
|
1026
1197
|
return false;
|
|
@@ -1287,6 +1458,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1287
1458
|
openUrl,
|
|
1288
1459
|
launchApp,
|
|
1289
1460
|
terminateApp,
|
|
1461
|
+
listApps,
|
|
1290
1462
|
watchApp,
|
|
1291
1463
|
playOnMicrophone,
|
|
1292
1464
|
pushFile,
|
|
@@ -1297,9 +1469,18 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1297
1469
|
setWifiBandwidth,
|
|
1298
1470
|
startRecording,
|
|
1299
1471
|
stopRecording,
|
|
1472
|
+
startAppLogCapture,
|
|
1473
|
+
stopAppLogCapture,
|
|
1474
|
+
startEventCapture,
|
|
1475
|
+
stopEventCapture,
|
|
1476
|
+
streamAppLogCapture,
|
|
1477
|
+
streamEventCapture,
|
|
1300
1478
|
keepAlive,
|
|
1301
1479
|
disconnect,
|
|
1302
1480
|
startAdbTunnel,
|
|
1481
|
+
startTunnel,
|
|
1482
|
+
getTunnelStatus,
|
|
1483
|
+
stopTunnel,
|
|
1303
1484
|
sendAsset,
|
|
1304
1485
|
syncApp,
|
|
1305
1486
|
getConnectionState,
|
|
@@ -1435,6 +1616,11 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1435
1616
|
await sendRequest('terminateApp', { packageName });
|
|
1436
1617
|
};
|
|
1437
1618
|
|
|
1619
|
+
const listApps = async (): Promise<InstalledApp[]> => {
|
|
1620
|
+
const result = await sendRequest('listApps', {});
|
|
1621
|
+
return result.apps ?? [];
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1438
1624
|
const watchApp = async (packageName: string, onExit: LaunchAppExitCallback): Promise<AppWatch> => {
|
|
1439
1625
|
const { execId } = await withExitCallback('watch', onExit, (execId) =>
|
|
1440
1626
|
sendRequest('watchApp', { packageName, execId }),
|
|
@@ -1551,8 +1737,13 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1551
1737
|
await sendRequest('setWifiBandwidth', request);
|
|
1552
1738
|
};
|
|
1553
1739
|
|
|
1554
|
-
const startRecording = async (recordingOptions?: {
|
|
1555
|
-
|
|
1740
|
+
const startRecording = async (recordingOptions?: {
|
|
1741
|
+
quality?: RecordingQuality;
|
|
1742
|
+
persist?: PersistOption;
|
|
1743
|
+
}): Promise<void> => {
|
|
1744
|
+
const request: CommandRequestMap['startRecording'] = {
|
|
1745
|
+
...persistFields(recordingOptions?.persist),
|
|
1746
|
+
};
|
|
1556
1747
|
if (recordingOptions?.quality !== undefined) {
|
|
1557
1748
|
if (
|
|
1558
1749
|
!Number.isInteger(recordingOptions.quality) ||
|
|
@@ -1579,6 +1770,53 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1579
1770
|
return downloadUrl;
|
|
1580
1771
|
};
|
|
1581
1772
|
|
|
1773
|
+
const startAppLogCapture = async (captureOptions: {
|
|
1774
|
+
bundleId: string;
|
|
1775
|
+
persist?: PersistOption;
|
|
1776
|
+
}): Promise<void> => {
|
|
1777
|
+
if (!captureOptions.bundleId) {
|
|
1778
|
+
throw new Error('bundleId must be a non-empty string');
|
|
1779
|
+
}
|
|
1780
|
+
await sendRequest('startAppLogCapture', {
|
|
1781
|
+
bundleId: captureOptions.bundleId,
|
|
1782
|
+
...persistFields(captureOptions.persist),
|
|
1783
|
+
});
|
|
1784
|
+
};
|
|
1785
|
+
|
|
1786
|
+
const stopAppLogCapture = async (): Promise<void> => {
|
|
1787
|
+
await sendRequest('stopAppLogCapture', {});
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1790
|
+
const startEventCapture = async (captureOptions?: { persist?: PersistOption }): Promise<void> => {
|
|
1791
|
+
await sendRequest('startEventCapture', { ...persistFields(captureOptions?.persist) });
|
|
1792
|
+
};
|
|
1793
|
+
|
|
1794
|
+
const stopEventCapture = async (): Promise<void> => {
|
|
1795
|
+
await sendRequest('stopEventCapture', {});
|
|
1796
|
+
};
|
|
1797
|
+
|
|
1798
|
+
const streamAppLogCapture = (handlers: {
|
|
1799
|
+
onLine: (line: SessionLogLine) => void;
|
|
1800
|
+
onError?: (error: Error) => void;
|
|
1801
|
+
}): (() => void) =>
|
|
1802
|
+
streamSessionEntries<SessionLogLine>({
|
|
1803
|
+
url: `${options.apiUrl}/session/applogs/events`,
|
|
1804
|
+
token: options.token,
|
|
1805
|
+
onEntry: handlers.onLine,
|
|
1806
|
+
onError: handlers.onError,
|
|
1807
|
+
});
|
|
1808
|
+
|
|
1809
|
+
const streamEventCapture = (handlers: {
|
|
1810
|
+
onEvent: (event: SessionEvent) => void;
|
|
1811
|
+
onError?: (error: Error) => void;
|
|
1812
|
+
}): (() => void) =>
|
|
1813
|
+
streamSessionEntries<SessionEvent>({
|
|
1814
|
+
url: `${options.apiUrl}/session/events/events`,
|
|
1815
|
+
token: options.token,
|
|
1816
|
+
onEntry: handlers.onEvent,
|
|
1817
|
+
onError: handlers.onError,
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1582
1820
|
const keepAlive = (): void => {
|
|
1583
1821
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
1584
1822
|
return;
|
|
@@ -1643,6 +1881,36 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
|
|
|
1643
1881
|
return tunnel;
|
|
1644
1882
|
};
|
|
1645
1883
|
|
|
1884
|
+
const requireAdbUrl = (): string => {
|
|
1885
|
+
if (!options.adbUrl) {
|
|
1886
|
+
throw new Error('adbUrl is required to manage a destination tunnel.');
|
|
1887
|
+
}
|
|
1888
|
+
return options.adbUrl;
|
|
1889
|
+
};
|
|
1890
|
+
|
|
1891
|
+
const startTunnel = async (tunnelOptions: DestinationTunnelOptions): Promise<DestinationTunnel> => {
|
|
1892
|
+
return startDestinationTcpTunnel(deriveDestinationTunnelURL(requireAdbUrl()), options.token, {
|
|
1893
|
+
selectors: tunnelOptions.selectors,
|
|
1894
|
+
inspection: {
|
|
1895
|
+
enabled: true,
|
|
1896
|
+
captureBodies: false,
|
|
1897
|
+
...(tunnelOptions.inspection ?? {}),
|
|
1898
|
+
},
|
|
1899
|
+
...(tunnelOptions.onInspectionEvent ? { onInspectionEvent: tunnelOptions.onInspectionEvent } : {}),
|
|
1900
|
+
...(tunnelOptions.onInspectionError ? { onInspectionError: tunnelOptions.onInspectionError } : {}),
|
|
1901
|
+
...(tunnelOptions.window === undefined ? {} : { window: tunnelOptions.window }),
|
|
1902
|
+
logLevel: tunnelOptions.logLevel ?? logLevel,
|
|
1903
|
+
});
|
|
1904
|
+
};
|
|
1905
|
+
|
|
1906
|
+
const getTunnelStatus = async (): Promise<DestinationTunnelStatus> => {
|
|
1907
|
+
return getDestinationTunnelStatus(requireAdbUrl(), options.token);
|
|
1908
|
+
};
|
|
1909
|
+
|
|
1910
|
+
const stopTunnel = async (tunnelId: string): Promise<void> => {
|
|
1911
|
+
await stopDestinationTunnel(requireAdbUrl(), options.token, tunnelId);
|
|
1912
|
+
};
|
|
1913
|
+
|
|
1646
1914
|
const sendAsset = async (url: string, timeoutMs?: number): Promise<void> => {
|
|
1647
1915
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
1648
1916
|
return Promise.reject(new Error('WebSocket is not connected or connection is not open.'));
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { nodeProxyTransport } from './proxy-transport';
|
|
2
2
|
import { deriveDestinationTunnelStatusURL, deriveDestinationTunnelStopURL } from './destination-tunnel-url';
|
|
3
|
-
import {
|
|
3
|
+
import { readArray, readNonEmptyString, readRecord, readString } from './destination-tunnel-wire-reader';
|
|
4
|
+
import {
|
|
5
|
+
normalizeDestinationTunnelInspection,
|
|
6
|
+
type DestinationTunnelBindReport,
|
|
7
|
+
type DestinationTunnelInspectionConfig,
|
|
8
|
+
type DestinationTunnelSelectorReport,
|
|
9
|
+
} from '../destination-tunnel';
|
|
4
10
|
|
|
5
11
|
export interface DestinationTunnelStatus {
|
|
6
12
|
active?: {
|
|
7
13
|
tunnelId: string;
|
|
8
14
|
state: 'starting' | 'ready' | 'stopping';
|
|
9
|
-
|
|
15
|
+
selectors: DestinationTunnelSelectorReport[];
|
|
16
|
+
inspection: DestinationTunnelInspectionConfig;
|
|
10
17
|
};
|
|
11
18
|
lastFailure?: {
|
|
12
19
|
tunnelId: string;
|
|
@@ -15,7 +22,7 @@ export interface DestinationTunnelStatus {
|
|
|
15
22
|
lastDialFailure?: {
|
|
16
23
|
tunnelId: string;
|
|
17
24
|
connectionId: number;
|
|
18
|
-
|
|
25
|
+
selectorId: string;
|
|
19
26
|
reason: string;
|
|
20
27
|
osCode?: string;
|
|
21
28
|
};
|
|
@@ -74,19 +81,67 @@ function readActiveTunnel(value: unknown): NonNullable<DestinationTunnelStatus['
|
|
|
74
81
|
return {
|
|
75
82
|
tunnelId: readNonEmptyString(active, 'tunnelId'),
|
|
76
83
|
state,
|
|
77
|
-
|
|
84
|
+
selectors: readArray(active, 'selectors').map((selector, index) =>
|
|
85
|
+
readSelectorReport(selector, `selector-${index + 1}`),
|
|
86
|
+
),
|
|
87
|
+
inspection: readInspection(active),
|
|
78
88
|
};
|
|
79
89
|
}
|
|
80
90
|
|
|
81
|
-
function
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
function readInspection(active: Record<string, unknown>): DestinationTunnelInspectionConfig {
|
|
92
|
+
const inspection = readRecord(active['inspection'], 'inspection');
|
|
93
|
+
const enabled = inspection['enabled'];
|
|
94
|
+
const captureBodies = inspection['captureBodies'];
|
|
95
|
+
const persist = inspection['persist'];
|
|
96
|
+
if (typeof enabled !== 'boolean' || typeof captureBodies !== 'boolean' || typeof persist !== 'boolean') {
|
|
97
|
+
throw new Error('inspection enabled, captureBodies, and persist must be booleans');
|
|
98
|
+
}
|
|
99
|
+
const maxBodyBytes = inspection['maxBodyBytes'];
|
|
100
|
+
const ttlSeconds = inspection['ttlSeconds'];
|
|
101
|
+
if (typeof maxBodyBytes !== 'number' || typeof ttlSeconds !== 'number') {
|
|
102
|
+
throw new Error('inspection maxBodyBytes and ttlSeconds must be numbers');
|
|
103
|
+
}
|
|
104
|
+
return normalizeDestinationTunnelInspection({
|
|
105
|
+
enabled,
|
|
106
|
+
captureBodies,
|
|
107
|
+
maxBodyBytes,
|
|
108
|
+
persist,
|
|
109
|
+
ttlSeconds,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readSelectorReport(value: unknown, expectedId: string): DestinationTunnelSelectorReport {
|
|
114
|
+
const report = readRecord(value, 'tunnel selector');
|
|
115
|
+
const id = readNonEmptyString(report, 'id');
|
|
116
|
+
if (id !== expectedId) {
|
|
117
|
+
throw new Error(`invalid tunnel selector id ${id}`);
|
|
118
|
+
}
|
|
119
|
+
const kind = readString(report, 'kind');
|
|
120
|
+
if (kind !== 'route' && kind !== 'domain') {
|
|
121
|
+
throw new Error(`invalid tunnel selector kind ${kind}`);
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
id,
|
|
125
|
+
kind,
|
|
126
|
+
value: readNonEmptyString(report, 'value'),
|
|
127
|
+
...(report['binds'] === undefined ? {} : { binds: readArray(report, 'binds').map(readBindReport) }),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readBindReport(value: unknown): DestinationTunnelBindReport {
|
|
132
|
+
const bind = readRecord(value, 'tunnel bind report');
|
|
133
|
+
const status = readString(bind, 'status');
|
|
134
|
+
if (status !== 'ok' && status !== 'conflict' && status !== 'error') {
|
|
135
|
+
throw new Error(`invalid tunnel bind status ${status}`);
|
|
136
|
+
}
|
|
137
|
+
const osCode = bind['osCode'];
|
|
138
|
+
if (osCode !== undefined && typeof osCode !== 'string') {
|
|
139
|
+
throw new Error('tunnel bind osCode must be a string');
|
|
86
140
|
}
|
|
87
141
|
return {
|
|
88
|
-
|
|
89
|
-
|
|
142
|
+
address: readNonEmptyString(bind, 'address'),
|
|
143
|
+
status,
|
|
144
|
+
...(osCode === undefined ? {} : { osCode }),
|
|
90
145
|
};
|
|
91
146
|
}
|
|
92
147
|
|
|
@@ -116,39 +171,8 @@ function readTunnelDialFailure(value: unknown): NonNullable<DestinationTunnelSta
|
|
|
116
171
|
return {
|
|
117
172
|
tunnelId: readNonEmptyString(failure, 'tunnelId'),
|
|
118
173
|
connectionId,
|
|
119
|
-
|
|
174
|
+
selectorId: readNonEmptyString(failure, 'selectorId'),
|
|
120
175
|
reason: readNonEmptyString(failure, 'reason'),
|
|
121
176
|
...(osCode === undefined ? {} : { osCode }),
|
|
122
177
|
};
|
|
123
178
|
}
|
|
124
|
-
|
|
125
|
-
function readRecord(value: unknown, name: string): Record<string, unknown> {
|
|
126
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
127
|
-
throw new Error(`${name} must be an object`);
|
|
128
|
-
}
|
|
129
|
-
return value as Record<string, unknown>;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function readArray(record: Record<string, unknown>, key: string): unknown[] {
|
|
133
|
-
const value = record[key];
|
|
134
|
-
if (!Array.isArray(value)) {
|
|
135
|
-
throw new Error(`${key} must be an array`);
|
|
136
|
-
}
|
|
137
|
-
return value;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function readString(record: Record<string, unknown>, key: string): string {
|
|
141
|
-
const value = record[key];
|
|
142
|
-
if (typeof value !== 'string') {
|
|
143
|
-
throw new Error(`${key} must be a string`);
|
|
144
|
-
}
|
|
145
|
-
return value;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function readNonEmptyString(record: Record<string, unknown>, key: string): string {
|
|
149
|
-
const value = readString(record, key);
|
|
150
|
-
if (value.length === 0) {
|
|
151
|
-
throw new Error(`${key} must not be empty`);
|
|
152
|
-
}
|
|
153
|
-
return value;
|
|
154
|
-
}
|