@limrun/api 0.59.0 → 0.60.0

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/destination-tunnel-dialer.d.mts +7 -0
  3. package/destination-tunnel-dialer.d.mts.map +1 -1
  4. package/destination-tunnel-dialer.d.ts +7 -0
  5. package/destination-tunnel-dialer.d.ts.map +1 -1
  6. package/destination-tunnel-dialer.js +15 -4
  7. package/destination-tunnel-dialer.js.map +1 -1
  8. package/destination-tunnel-dialer.mjs +13 -3
  9. package/destination-tunnel-dialer.mjs.map +1 -1
  10. package/destination-tunnel.d.mts +4 -0
  11. package/destination-tunnel.d.mts.map +1 -1
  12. package/destination-tunnel.d.ts +4 -0
  13. package/destination-tunnel.d.ts.map +1 -1
  14. package/destination-tunnel.js +2 -0
  15. package/destination-tunnel.js.map +1 -1
  16. package/destination-tunnel.mjs +2 -0
  17. package/destination-tunnel.mjs.map +1 -1
  18. package/index.d.mts +1 -0
  19. package/index.d.mts.map +1 -1
  20. package/index.d.ts +1 -0
  21. package/index.d.ts.map +1 -1
  22. package/index.js +4 -2
  23. package/index.js.map +1 -1
  24. package/index.mjs +1 -0
  25. package/index.mjs.map +1 -1
  26. package/internal/destination-tunnel-management.d.mts +2 -0
  27. package/internal/destination-tunnel-management.d.mts.map +1 -1
  28. package/internal/destination-tunnel-management.d.ts +2 -0
  29. package/internal/destination-tunnel-management.d.ts.map +1 -1
  30. package/internal/destination-tunnel-management.js +5 -0
  31. package/internal/destination-tunnel-management.js.map +1 -1
  32. package/internal/destination-tunnel-management.mjs +6 -1
  33. package/internal/destination-tunnel-management.mjs.map +1 -1
  34. package/internal/destination-tunnel-url.d.mts +2 -0
  35. package/internal/destination-tunnel-url.d.mts.map +1 -1
  36. package/internal/destination-tunnel-url.d.ts +2 -0
  37. package/internal/destination-tunnel-url.d.ts.map +1 -1
  38. package/internal/destination-tunnel-url.js +9 -2
  39. package/internal/destination-tunnel-url.js.map +1 -1
  40. package/internal/destination-tunnel-url.mjs +8 -2
  41. package/internal/destination-tunnel-url.mjs.map +1 -1
  42. package/ios-client.d.mts +6 -4
  43. package/ios-client.d.mts.map +1 -1
  44. package/ios-client.d.ts +6 -4
  45. package/ios-client.d.ts.map +1 -1
  46. package/ios-client.js +3 -1
  47. package/ios-client.js.map +1 -1
  48. package/ios-client.mjs +3 -1
  49. package/ios-client.mjs.map +1 -1
  50. package/ios-launch-environment.d.mts +3 -0
  51. package/ios-launch-environment.d.mts.map +1 -0
  52. package/ios-launch-environment.d.ts +3 -0
  53. package/ios-launch-environment.d.ts.map +1 -0
  54. package/ios-launch-environment.js +14 -0
  55. package/ios-launch-environment.js.map +1 -0
  56. package/ios-launch-environment.mjs +11 -0
  57. package/ios-launch-environment.mjs.map +1 -0
  58. package/package.json +22 -2
  59. package/resources/android-instances.d.mts +12 -0
  60. package/resources/android-instances.d.mts.map +1 -1
  61. package/resources/android-instances.d.ts +12 -0
  62. package/resources/android-instances.d.ts.map +1 -1
  63. package/resources/ios-instances.d.mts +12 -0
  64. package/resources/ios-instances.d.mts.map +1 -1
  65. package/resources/ios-instances.d.ts +12 -0
  66. package/resources/ios-instances.d.ts.map +1 -1
  67. package/src/destination-tunnel-dialer.ts +18 -3
  68. package/src/destination-tunnel.ts +6 -0
  69. package/src/index.ts +6 -0
  70. package/src/internal/destination-tunnel-management.ts +15 -1
  71. package/src/internal/destination-tunnel-url.ts +15 -2
  72. package/src/ios-client.ts +12 -5
  73. package/src/ios-launch-environment.ts +11 -0
  74. package/src/resources/android-instances.ts +14 -0
  75. package/src/resources/ios-instances.ts +14 -0
  76. package/src/tunnel-connector.ts +677 -0
  77. package/src/tunnel.ts +8 -6
  78. package/src/version.ts +1 -1
  79. package/tunnel-connector.d.mts +77 -0
  80. package/tunnel-connector.d.mts.map +1 -0
  81. package/tunnel-connector.d.ts +77 -0
  82. package/tunnel-connector.d.ts.map +1 -0
  83. package/tunnel-connector.js +561 -0
  84. package/tunnel-connector.js.map +1 -0
  85. package/tunnel-connector.mjs +557 -0
  86. package/tunnel-connector.mjs.map +1 -0
  87. package/tunnel.d.mts +2 -0
  88. package/tunnel.d.mts.map +1 -1
  89. package/tunnel.d.ts +2 -0
  90. package/tunnel.d.ts.map +1 -1
  91. package/tunnel.js +8 -6
  92. package/tunnel.js.map +1 -1
  93. package/tunnel.mjs +7 -6
  94. package/tunnel.mjs.map +1 -1
  95. package/version.d.mts +1 -1
  96. package/version.d.ts +1 -1
  97. package/version.js +1 -1
  98. package/version.mjs +1 -1
@@ -136,6 +136,8 @@ export type DestinationTunnelClientMessage =
136
136
  inspection: DestinationTunnelInspectionConfig;
137
137
  /** Default per-flow receive window this client grants. */
138
138
  window: number;
139
+ /** Persistent tunnel this session serves. The instance only echoes it in status. */
140
+ name?: string;
139
141
  }
140
142
  | {
141
143
  type: 'openOk';
@@ -160,6 +162,8 @@ export type DestinationTunnelServerMessage =
160
162
  tunnelId: string;
161
163
  selectors: DestinationTunnelSelectorReport[];
162
164
  configHash: string;
165
+ /** The persistent tunnel name the client sent in start, echoed back. */
166
+ name?: string;
163
167
  }
164
168
  | {
165
169
  type: 'open';
@@ -466,6 +470,7 @@ export function encodeDestinationTunnelClientMessage(message: DestinationTunnelC
466
470
  selectors,
467
471
  inspection,
468
472
  window: readWindow(record),
473
+ ...readOptionalString(record, 'name'),
469
474
  });
470
475
  }
471
476
  case 'openOk':
@@ -510,6 +515,7 @@ export function decodeDestinationTunnelServerMessage(value: unknown): Destinatio
510
515
  readSelectorReport(value, `selector-${index + 1}`),
511
516
  ),
512
517
  configHash: readString(message, 'configHash'),
518
+ ...readOptionalString(message, 'name'),
513
519
  };
514
520
  case 'open':
515
521
  return {
package/src/index.ts CHANGED
@@ -13,6 +13,12 @@ export {
13
13
  type DestinationTcpTunnel,
14
14
  type DestinationTcpTunnelOptions,
15
15
  } from './destination-tunnel-dialer';
16
+ export {
17
+ runTunnel,
18
+ type TunnelConnector,
19
+ type TunnelConnectorEvent,
20
+ type TunnelConnectorOptions,
21
+ } from './tunnel-connector';
16
22
  export {
17
23
  decodeDestinationTunnelInspectionSSEEvent,
18
24
  deriveDestinationTunnelInspectionURL,
@@ -1,6 +1,12 @@
1
1
  import { nodeProxyTransport } from './proxy-transport';
2
2
  import { deriveDestinationTunnelStatusURL, deriveDestinationTunnelStopURL } from './destination-tunnel-url';
3
- import { readArray, readNonEmptyString, readRecord, readString } from './destination-tunnel-wire-reader';
3
+ import {
4
+ readArray,
5
+ readNonEmptyString,
6
+ readOptionalString,
7
+ readRecord,
8
+ readString,
9
+ } from './destination-tunnel-wire-reader';
4
10
  import {
5
11
  normalizeDestinationTunnelInspection,
6
12
  type DestinationTunnelBindReport,
@@ -11,6 +17,8 @@ import {
11
17
  export interface DestinationTunnelStatus {
12
18
  active?: {
13
19
  tunnelId: string;
20
+ /** Persistent tunnel the session serves, absent for a one-instance tunnel. */
21
+ name?: string;
14
22
  state: 'starting' | 'ready' | 'stopping';
15
23
  selectors: DestinationTunnelSelectorReport[];
16
24
  inspection: DestinationTunnelInspectionConfig;
@@ -28,12 +36,16 @@ export interface DestinationTunnelStatus {
28
36
  };
29
37
  }
30
38
 
39
+ /** Status and stop answer at once; a wedged instance must not hold its caller for minutes. */
40
+ const MANAGEMENT_TIMEOUT_MS = 10_000;
41
+
31
42
  export async function getDestinationTunnelStatus(
32
43
  apiUrl: string,
33
44
  token: string,
34
45
  ): Promise<DestinationTunnelStatus> {
35
46
  const response = await nodeProxyTransport.fetch(deriveDestinationTunnelStatusURL(apiUrl).toString(), {
36
47
  headers: { Authorization: `Bearer ${token}` },
48
+ signal: AbortSignal.timeout(MANAGEMENT_TIMEOUT_MS),
37
49
  });
38
50
  if (!response.ok) {
39
51
  throw new Error(`getTunnelStatus failed: ${response.status} ${await response.text()}`);
@@ -50,6 +62,7 @@ export async function stopDestinationTunnel(apiUrl: string, token: string, tunne
50
62
  {
51
63
  method: 'DELETE',
52
64
  headers: { Authorization: `Bearer ${token}` },
65
+ signal: AbortSignal.timeout(MANAGEMENT_TIMEOUT_MS),
53
66
  },
54
67
  );
55
68
  if (!response.ok) {
@@ -80,6 +93,7 @@ function readActiveTunnel(value: unknown): NonNullable<DestinationTunnelStatus['
80
93
  }
81
94
  return {
82
95
  tunnelId: readNonEmptyString(active, 'tunnelId'),
96
+ ...readOptionalString(active, 'name'),
83
97
  state,
84
98
  selectors: readArray(active, 'selectors').map((selector, index) =>
85
99
  readSelectorReport(selector, `selector-${index + 1}`),
@@ -1,4 +1,4 @@
1
- /** The endpoint at `path` under an instance URL, without query or fragment. */
1
+ /** The endpoint at `path` under a base URL, without query or fragment. */
2
2
  function deriveEndpointURL(apiUrl: string, path: string): URL {
3
3
  const url = new URL(apiUrl);
4
4
  if (
@@ -26,7 +26,20 @@ export function deriveAdbHttpURL(adbUrl: string, path: string): URL {
26
26
  }
27
27
 
28
28
  export function deriveDestinationTunnelURL(apiUrl: string): string {
29
- const url = deriveEndpointURL(apiUrl, 'tunnel');
29
+ return webSocketURL(deriveEndpointURL(apiUrl, 'tunnel'));
30
+ }
31
+
32
+ /** The control WebSocket of a persistent tunnel, under the Limrun API base URL. */
33
+ export function deriveTunnelConnectURL(baseURL: string, organizationId: string, name: string): string {
34
+ return webSocketURL(
35
+ deriveEndpointURL(
36
+ baseURL,
37
+ `v1/organizations/${encodeURIComponent(organizationId)}/tunnels/${encodeURIComponent(name)}/connect`,
38
+ ),
39
+ );
40
+ }
41
+
42
+ function webSocketURL(url: URL): string {
30
43
  url.protocol = url.protocol === 'https:' || url.protocol === 'wss:' ? 'wss:' : 'ws:';
31
44
  return url.toString();
32
45
  }
package/src/ios-client.ts CHANGED
@@ -450,21 +450,23 @@ export type AppWatch = {
450
450
  stop: () => Promise<void>;
451
451
  };
452
452
 
453
- type LaunchAppExitOptions = {
453
+ type LaunchAppCommonOptions = {
454
+ /** App environment variables. Requires relaunch; reserved keys and existing launch variables are ignored. */
455
+ env?: Record<string, string>;
454
456
  /** Called once when the launched app exits, crashes, or is terminated. */
455
457
  onExit?: LaunchAppExitCallback;
456
458
  };
457
459
 
458
- type StandardLaunchAppOptions = LaunchAppExitOptions & {
460
+ type StandardLaunchAppOptions = LaunchAppCommonOptions & {
459
461
  /**
460
462
  * Launch behavior when the app may already be running.
461
- * Defaults to `ForegroundIfRunning` server-side.
463
+ * Defaults to `ForegroundIfRunning`, or `RelaunchIfRunning` when env is nonempty.
462
464
  */
463
465
  mode?: LaunchAppMode;
464
466
  runtime?: undefined;
465
467
  };
466
468
 
467
- type RuntimeLaunchAppOptions = LaunchAppExitOptions & {
469
+ type RuntimeLaunchAppOptions = LaunchAppCommonOptions & {
468
470
  /** Runtime launches must relaunch so runtime injection is applied. */
469
471
  mode?: Extract<LaunchAppMode, 'RelaunchIfRunning'>;
470
472
  /** Optional app runtime to attach during launch. */
@@ -2368,7 +2370,11 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2368
2370
  new Error('launchApp runtime launches require RelaunchIfRunning so runtime injection is applied.'),
2369
2371
  );
2370
2372
  }
2371
- const mode = launchOptions.runtime ? 'RelaunchIfRunning' : launchOptions.mode;
2373
+ const hasEnvironment = Object.keys(launchOptions.env ?? {}).length > 0;
2374
+ const mode =
2375
+ launchOptions.runtime ? 'RelaunchIfRunning' : (
2376
+ launchOptions.mode ?? (hasEnvironment ? 'RelaunchIfRunning' : undefined)
2377
+ );
2372
2378
  const onExit = launchOptions.onExit;
2373
2379
  const execId = onExit ? generateId() : undefined;
2374
2380
  if (execId && onExit) {
@@ -2378,6 +2384,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2378
2384
  bundleId,
2379
2385
  mode,
2380
2386
  runtime: launchOptions.runtime,
2387
+ env: launchOptions.env,
2381
2388
  execId,
2382
2389
  }).catch((error) => {
2383
2390
  if (execId) {
@@ -0,0 +1,11 @@
1
+ /** Parse repeatable KEY=VALUE entries; the last occurrence of a key wins. */
2
+ export function parseLaunchEnvironment(entries: string[]): Record<string, string> {
3
+ const env = Object.fromEntries(
4
+ entries.map((entry) => {
5
+ const separator = entry.indexOf('=');
6
+ if (separator < 1) throw new Error('launch environment must use KEY=VALUE');
7
+ return [entry.slice(0, separator), entry.slice(separator + 1)];
8
+ }),
9
+ );
10
+ return env;
11
+ }
@@ -88,6 +88,13 @@ export namespace AndroidInstance {
88
88
  * 10m, 3h. Default is "0" which means no hard timeout.
89
89
  */
90
90
  hardTimeout?: string;
91
+
92
+ /**
93
+ * Names a persistent tunnel (`lim tunnel run --name`) that serves this
94
+ * instance. Creation fails while that tunnel is offline, and the instance
95
+ * becomes ready only after the tunnel attaches.
96
+ */
97
+ tunnel?: string;
91
98
  }
92
99
 
93
100
  export interface Status {
@@ -241,6 +248,13 @@ export namespace AndroidInstanceCreateParams {
241
248
  region?: string;
242
249
 
243
250
  sandbox?: Spec.Sandbox;
251
+
252
+ /**
253
+ * Names a persistent tunnel (`lim tunnel run --name`) that serves this
254
+ * instance. Creation fails while that tunnel is offline, and the instance
255
+ * becomes ready only after the tunnel attaches.
256
+ */
257
+ tunnel?: string;
244
258
  }
245
259
 
246
260
  export namespace Spec {
@@ -88,6 +88,13 @@ export namespace IosInstance {
88
88
  * 10m, 3h. Default is "0" which means no hard timeout.
89
89
  */
90
90
  hardTimeout?: string;
91
+
92
+ /**
93
+ * Names a persistent tunnel (`lim tunnel run --name`) that serves this
94
+ * instance. Creation fails while that tunnel is offline, and the instance
95
+ * becomes ready only after the tunnel attaches.
96
+ */
97
+ tunnel?: string;
91
98
  }
92
99
 
93
100
  export interface Status {
@@ -230,6 +237,13 @@ export namespace IosInstanceCreateParams {
230
237
  * the first.
231
238
  */
232
239
  region?: string;
240
+
241
+ /**
242
+ * Names a persistent tunnel (`lim tunnel run --name`) that serves this
243
+ * instance. Creation fails while that tunnel is offline, and the instance
244
+ * becomes ready only after the tunnel attaches.
245
+ */
246
+ tunnel?: string;
233
247
  }
234
248
 
235
249
  export namespace Spec {