@limrun/api 0.58.1 → 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 +75 -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 +58 -7
  43. package/ios-client.d.mts.map +1 -1
  44. package/ios-client.d.ts +58 -7
  45. package/ios-client.d.ts.map +1 -1
  46. package/ios-client.js +58 -30
  47. package/ios-client.js.map +1 -1
  48. package/ios-client.mjs +58 -30
  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 +137 -43
  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
package/src/ios-client.ts CHANGED
@@ -405,26 +405,68 @@ export type DetoxLaunchRuntime = {
405
405
 
406
406
  export type LaunchAppRuntime = DetoxLaunchRuntime;
407
407
 
408
- export type LaunchAppExitCallback = (logs: string[]) => Promise<void>;
408
+ /**
409
+ * Why a launched or watched app shut down.
410
+ * - `crash`: the process died on a fatal signal; `crash` details are attached.
411
+ * - `exit`: the process ended without a crash and was not stopped through the SDK.
412
+ * - `terminated`: the app was stopped via {@link InstanceClient.terminateApp} or a relaunch.
413
+ */
414
+ export type AppExitReason = 'crash' | 'exit' | 'terminated';
409
415
 
410
- type LaunchAppExitOptions = {
416
+ export type AppCrashInfo = {
417
+ processName: string;
418
+ pid: number;
419
+ /** Exception type and signal, for example `EXC_BAD_ACCESS (SIGSEGV) at 0x10`. */
420
+ shortMsg: string;
421
+ /** The crash message the app left, such as an uncaught exception's reason, else `shortMsg`. */
422
+ longMsg: string;
411
423
  /**
412
- * Called when the launched app exits. The logs array contains one entry per
413
- * stdout/stderr line produced by that execution, as reported by limulator.
424
+ * The crashed thread's frames, with symbol names for the app's own code, followed by the
425
+ * binary images they belong to. Symbolicate file and line with your dSYM, for example
426
+ * `atos -o App.dSYM -l <load address> <frame address>`.
414
427
  */
428
+ stackTrace: string;
429
+ timeMillis: number;
430
+ };
431
+
432
+ export type AppExitInfo = {
433
+ bundleId: string;
434
+ reason: AppExitReason;
435
+ crash?: AppCrashInfo;
436
+ };
437
+
438
+ /**
439
+ * Called when the app shuts down. For a launched app the logs array contains one entry per
440
+ * stdout/stderr line that launch produced; a watched app reports no logs. `info` carries the
441
+ * exit reason plus crash details when applicable.
442
+ */
443
+ export type LaunchAppExitCallback = (logs: string[], info: AppExitInfo) => Promise<void> | void;
444
+
445
+ /** Handle for an app watch registered via {@link InstanceClient.watchApp}. */
446
+ export type AppWatch = {
447
+ /** Identifier of this watch on the server. */
448
+ execId: string;
449
+ /** Cancels the watch; the onExit callback will not be invoked afterwards. */
450
+ stop: () => Promise<void>;
451
+ };
452
+
453
+ type LaunchAppCommonOptions = {
454
+ /** App environment variables. Requires relaunch; reserved keys and existing launch variables are ignored. */
455
+ env?: Record<string, string>;
456
+ /** Called once when the launched app exits, crashes, or is terminated. */
415
457
  onExit?: LaunchAppExitCallback;
416
458
  };
417
459
 
418
- type StandardLaunchAppOptions = LaunchAppExitOptions & {
460
+ type StandardLaunchAppOptions = LaunchAppCommonOptions & {
419
461
  /**
420
462
  * Launch behavior when the app may already be running.
421
- * Defaults to `ForegroundIfRunning` server-side.
463
+ * Defaults to `ForegroundIfRunning`, or `RelaunchIfRunning` when env is nonempty.
422
464
  */
423
465
  mode?: LaunchAppMode;
424
466
  runtime?: undefined;
425
467
  };
426
468
 
427
- type RuntimeLaunchAppOptions = LaunchAppExitOptions & {
469
+ type RuntimeLaunchAppOptions = LaunchAppCommonOptions & {
428
470
  /** Runtime launches must relaunch so runtime injection is applied. */
429
471
  mode?: Extract<LaunchAppMode, 'RelaunchIfRunning'>;
430
472
  /** Optional app runtime to attach during launch. */
@@ -722,6 +764,20 @@ export type InstanceClient = {
722
764
  */
723
765
  terminateApp: (bundleId: string) => Promise<void>;
724
766
 
767
+ /**
768
+ * Watch an app's exit or crash without launching it through {@link launchApp}, for example
769
+ * an app opened with {@link openUrl} or already running. The callback fires once, with the
770
+ * exit reason and crash details, the next time any process of the app exits. Watched apps
771
+ * report no logs.
772
+ *
773
+ * The app does not need to be running yet: if it starts later, it is picked up automatically.
774
+ *
775
+ * @param bundleId Bundle identifier of the app to watch
776
+ * @param onExit Called once when the watched app shuts down
777
+ * @returns A handle whose `stop()` cancels the watch
778
+ */
779
+ watchApp: (bundleId: string, onExit: LaunchAppExitCallback) => Promise<AppWatch>;
780
+
725
781
  /**
726
782
  * Fetch the last N lines of app logs (combined stdout/stderr)
727
783
  * @param bundleId Bundle identifier of the app
@@ -1330,6 +1386,17 @@ type SimctlRequest = {
1330
1386
  /**
1331
1387
  * Generic server response with optional error
1332
1388
  */
1389
+ /** appExit as the server sends it; its logs field is an array, unlike appLogTailResult's. */
1390
+ type AppExitMessage = {
1391
+ type: 'appExit';
1392
+ execId: string;
1393
+ bundleId: string;
1394
+ reason?: AppExitReason;
1395
+ crash?: AppCrashInfo;
1396
+ logs?: string[];
1397
+ logLineCount?: number;
1398
+ };
1399
+
1333
1400
  type ServerResponse = {
1334
1401
  state?: FoldState;
1335
1402
  type: string;
@@ -1818,14 +1885,8 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
1818
1885
  return false;
1819
1886
  }
1820
1887
 
1821
- const { execId, bundleId, logLineCount } = message;
1822
- if (
1823
- typeof execId !== 'string' ||
1824
- typeof bundleId !== 'string' ||
1825
- typeof logLineCount !== 'number' ||
1826
- !Number.isInteger(logLineCount) ||
1827
- logLineCount < 0
1828
- ) {
1888
+ const { execId, bundleId } = message;
1889
+ if (typeof execId !== 'string' || typeof bundleId !== 'string') {
1829
1890
  logger.warn('Received malformed appExit message:', message);
1830
1891
  return true;
1831
1892
  }
@@ -1865,6 +1926,8 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
1865
1926
  toggleKeyboardResult: () => undefined,
1866
1927
  launchAppResult: () => undefined,
1867
1928
  terminateAppResult: () => undefined,
1929
+ watchAppResult: () => undefined,
1930
+ unwatchAppResult: () => undefined,
1868
1931
  appLogTailResult: (msg) => msg.logs ?? '',
1869
1932
  listAppsResult: (msg) => JSON.parse(msg.apps || '[]') as InstalledApp[],
1870
1933
  listOpenFilesResult: (msg) => msg.files || [],
@@ -2071,6 +2134,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2071
2134
  toggleKeyboard,
2072
2135
  launchApp,
2073
2136
  terminateApp,
2137
+ watchApp,
2074
2138
  appLogTail,
2075
2139
  streamAppLog,
2076
2140
  streamSyslog,
@@ -2306,42 +2370,21 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2306
2370
  new Error('launchApp runtime launches require RelaunchIfRunning so runtime injection is applied.'),
2307
2371
  );
2308
2372
  }
2309
- 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
+ );
2310
2378
  const onExit = launchOptions.onExit;
2311
2379
  const execId = onExit ? generateId() : undefined;
2312
2380
  if (execId && onExit) {
2313
- registerServerNotification('appExit', execId, async (message) => {
2314
- const { bundleId, logLineCount } = message;
2315
- if (
2316
- typeof bundleId !== 'string' ||
2317
- typeof logLineCount !== 'number' ||
2318
- !Number.isInteger(logLineCount) ||
2319
- logLineCount < 0
2320
- ) {
2321
- logger.warn(`Received malformed appExit payload for execId ${execId}:`, message);
2322
- return;
2323
- }
2324
-
2325
- let logs: string[] = [];
2326
- try {
2327
- logs = splitAppLogTail(
2328
- await sendRequest<string>('appLogTail', { bundleId, lines: logLineCount }),
2329
- );
2330
- } catch (error) {
2331
- logger.error(`Failed to fetch app logs for exit execId ${execId}:`, error);
2332
- }
2333
-
2334
- try {
2335
- await onExit(logs);
2336
- } catch (error) {
2337
- logger.error(`Error in onExit callback for execId ${execId}:`, error);
2338
- }
2339
- });
2381
+ registerServerNotification('appExit', execId, appExitHandler(execId, onExit));
2340
2382
  }
2341
2383
  return sendRequest<void>('launchApp', {
2342
2384
  bundleId,
2343
2385
  mode,
2344
2386
  runtime: launchOptions.runtime,
2387
+ env: launchOptions.env,
2345
2388
  execId,
2346
2389
  }).catch((error) => {
2347
2390
  if (execId) {
@@ -2351,6 +2394,57 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2351
2394
  });
2352
2395
  };
2353
2396
 
2397
+ /** Delivers one appExit to the callback, fetching the logs from servers that predate inline logs. */
2398
+ const appExitHandler =
2399
+ (execId: string, onExit: LaunchAppExitCallback): ServerNotificationHandler =>
2400
+ async (response) => {
2401
+ const message = response as unknown as AppExitMessage;
2402
+ let logs: string[] = [];
2403
+ if (Array.isArray(message.logs)) {
2404
+ logs = message.logs.map(String);
2405
+ } else if (Number.isInteger(message.logLineCount) && (message.logLineCount ?? 0) > 0) {
2406
+ try {
2407
+ logs = splitAppLogTail(
2408
+ await sendRequest<string>('appLogTail', {
2409
+ bundleId: message.bundleId,
2410
+ lines: message.logLineCount,
2411
+ }),
2412
+ );
2413
+ } catch (error) {
2414
+ logger.error(`Failed to fetch app logs for exit execId ${execId}:`, error);
2415
+ }
2416
+ }
2417
+ const info: AppExitInfo = {
2418
+ bundleId: message.bundleId,
2419
+ // Servers before exit reasons only reported that the app ended.
2420
+ reason: message.reason ?? 'exit',
2421
+ ...(message.crash ? { crash: message.crash } : {}),
2422
+ };
2423
+ try {
2424
+ await onExit(logs, info);
2425
+ } catch (error) {
2426
+ logger.error(`Error in onExit callback for execId ${execId}:`, error);
2427
+ }
2428
+ };
2429
+
2430
+ const watchApp = async (bundleId: string, onExit: LaunchAppExitCallback): Promise<AppWatch> => {
2431
+ const execId = generateId();
2432
+ registerServerNotification('appExit', execId, appExitHandler(execId, onExit));
2433
+ try {
2434
+ await sendRequest<void>('watchApp', { bundleId, execId });
2435
+ } catch (error) {
2436
+ deleteServerNotification('appExit', execId);
2437
+ throw error;
2438
+ }
2439
+ return {
2440
+ execId,
2441
+ stop: async () => {
2442
+ deleteServerNotification('appExit', execId);
2443
+ await sendRequest<void>('unwatchApp', { execId });
2444
+ },
2445
+ };
2446
+ };
2447
+
2354
2448
  const terminateApp = (bundleId: string): Promise<void> => {
2355
2449
  return sendRequest<void>('terminateApp', { bundleId });
2356
2450
  };
@@ -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 {