@limrun/api 0.65.1 → 0.67.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 (141) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +64 -0
  3. package/client.d.mts +7 -1
  4. package/client.d.mts.map +1 -1
  5. package/client.d.ts +7 -1
  6. package/client.d.ts.map +1 -1
  7. package/client.js +17 -3
  8. package/client.js.map +1 -1
  9. package/client.mjs +17 -3
  10. package/client.mjs.map +1 -1
  11. package/exec-client.d.mts +3 -6
  12. package/exec-client.d.mts.map +1 -1
  13. package/exec-client.d.ts +3 -6
  14. package/exec-client.d.ts.map +1 -1
  15. package/exec-client.js +4 -4
  16. package/exec-client.js.map +1 -1
  17. package/exec-client.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/internal/tslib.js +20 -20
  23. package/ios-client.d.mts +76 -2
  24. package/ios-client.d.mts.map +1 -1
  25. package/ios-client.d.ts +76 -2
  26. package/ios-client.d.ts.map +1 -1
  27. package/ios-client.js +88 -16
  28. package/ios-client.js.map +1 -1
  29. package/ios-client.mjs +88 -16
  30. package/ios-client.mjs.map +1 -1
  31. package/lib/asset-permissions.d.mts +45 -0
  32. package/lib/asset-permissions.d.mts.map +1 -0
  33. package/lib/asset-permissions.d.ts +45 -0
  34. package/lib/asset-permissions.d.ts.map +1 -0
  35. package/lib/asset-permissions.js +37 -0
  36. package/lib/asset-permissions.js.map +1 -0
  37. package/lib/asset-permissions.mjs +33 -0
  38. package/lib/asset-permissions.mjs.map +1 -0
  39. package/lib/workload-credentials.d.mts +14 -0
  40. package/lib/workload-credentials.d.mts.map +1 -0
  41. package/lib/workload-credentials.d.ts +14 -0
  42. package/lib/workload-credentials.d.ts.map +1 -0
  43. package/lib/workload-credentials.js +78 -0
  44. package/lib/workload-credentials.js.map +1 -0
  45. package/lib/workload-credentials.mjs +41 -0
  46. package/lib/workload-credentials.mjs.map +1 -0
  47. package/package.json +12 -2
  48. package/resources/access-tokens.d.mts +38 -0
  49. package/resources/access-tokens.d.mts.map +1 -0
  50. package/resources/access-tokens.d.ts +38 -0
  51. package/resources/access-tokens.d.ts.map +1 -0
  52. package/resources/access-tokens.js +16 -0
  53. package/resources/access-tokens.js.map +1 -0
  54. package/resources/access-tokens.mjs +12 -0
  55. package/resources/access-tokens.mjs.map +1 -0
  56. package/resources/android-instances.d.mts +5 -0
  57. package/resources/android-instances.d.mts.map +1 -1
  58. package/resources/android-instances.d.ts +5 -0
  59. package/resources/android-instances.d.ts.map +1 -1
  60. package/resources/assets.d.mts +25 -1
  61. package/resources/assets.d.mts.map +1 -1
  62. package/resources/assets.d.ts +25 -1
  63. package/resources/assets.d.ts.map +1 -1
  64. package/resources/assets.js +12 -0
  65. package/resources/assets.js.map +1 -1
  66. package/resources/assets.mjs +12 -0
  67. package/resources/assets.mjs.map +1 -1
  68. package/resources/daemon-client-shared.d.mts +2 -36
  69. package/resources/daemon-client-shared.d.mts.map +1 -1
  70. package/resources/daemon-client-shared.d.ts +2 -36
  71. package/resources/daemon-client-shared.d.ts.map +1 -1
  72. package/resources/daemon-client-shared.js +2 -21
  73. package/resources/daemon-client-shared.js.map +1 -1
  74. package/resources/daemon-client-shared.mjs +1 -20
  75. package/resources/daemon-client-shared.mjs.map +1 -1
  76. package/resources/gradle-instances-helpers.d.mts +2 -15
  77. package/resources/gradle-instances-helpers.d.mts.map +1 -1
  78. package/resources/gradle-instances-helpers.d.ts +2 -15
  79. package/resources/gradle-instances-helpers.d.ts.map +1 -1
  80. package/resources/gradle-instances-helpers.js +3 -11
  81. package/resources/gradle-instances-helpers.js.map +1 -1
  82. package/resources/gradle-instances-helpers.mjs +4 -12
  83. package/resources/gradle-instances-helpers.mjs.map +1 -1
  84. package/resources/gradle-instances.d.mts +5 -0
  85. package/resources/gradle-instances.d.mts.map +1 -1
  86. package/resources/gradle-instances.d.ts +5 -0
  87. package/resources/gradle-instances.d.ts.map +1 -1
  88. package/resources/index.d.mts +1 -0
  89. package/resources/index.d.mts.map +1 -1
  90. package/resources/index.d.ts +1 -0
  91. package/resources/index.d.ts.map +1 -1
  92. package/resources/index.js +3 -1
  93. package/resources/index.js.map +1 -1
  94. package/resources/index.mjs +1 -0
  95. package/resources/index.mjs.map +1 -1
  96. package/resources/ios-instances.d.mts +5 -0
  97. package/resources/ios-instances.d.mts.map +1 -1
  98. package/resources/ios-instances.d.ts +5 -0
  99. package/resources/ios-instances.d.ts.map +1 -1
  100. package/resources/scoped-tokens.d.mts +9 -16
  101. package/resources/scoped-tokens.d.mts.map +1 -1
  102. package/resources/scoped-tokens.d.ts +9 -16
  103. package/resources/scoped-tokens.d.ts.map +1 -1
  104. package/resources/scoped-tokens.js +3 -3
  105. package/resources/scoped-tokens.js.map +1 -1
  106. package/resources/scoped-tokens.mjs +3 -3
  107. package/resources/scoped-tokens.mjs.map +1 -1
  108. package/resources/xcode-instances-helpers.d.mts +5 -38
  109. package/resources/xcode-instances-helpers.d.mts.map +1 -1
  110. package/resources/xcode-instances-helpers.d.ts +5 -38
  111. package/resources/xcode-instances-helpers.d.ts.map +1 -1
  112. package/resources/xcode-instances-helpers.js +7 -29
  113. package/resources/xcode-instances-helpers.js.map +1 -1
  114. package/resources/xcode-instances-helpers.mjs +8 -30
  115. package/resources/xcode-instances-helpers.mjs.map +1 -1
  116. package/resources/xcode-instances.d.mts +5 -0
  117. package/resources/xcode-instances.d.mts.map +1 -1
  118. package/resources/xcode-instances.d.ts +5 -0
  119. package/resources/xcode-instances.d.ts.map +1 -1
  120. package/src/client.ts +29 -2
  121. package/src/exec-client.ts +3 -0
  122. package/src/index.ts +2 -0
  123. package/src/ios-client.ts +189 -21
  124. package/src/lib/asset-permissions.ts +67 -0
  125. package/src/lib/workload-credentials.ts +44 -0
  126. package/src/resources/access-tokens.ts +46 -0
  127. package/src/resources/android-instances.ts +6 -0
  128. package/src/resources/assets.ts +38 -0
  129. package/src/resources/daemon-client-shared.ts +3 -48
  130. package/src/resources/gradle-instances-helpers.ts +3 -30
  131. package/src/resources/gradle-instances.ts +6 -0
  132. package/src/resources/index.ts +2 -0
  133. package/src/resources/ios-instances.ts +6 -0
  134. package/src/resources/scoped-tokens.ts +10 -17
  135. package/src/resources/xcode-instances-helpers.ts +9 -75
  136. package/src/resources/xcode-instances.ts +6 -0
  137. package/src/version.ts +1 -1
  138. package/version.d.mts +1 -1
  139. package/version.d.ts +1 -1
  140. package/version.js +1 -1
  141. package/version.mjs +1 -1
@@ -94,6 +94,11 @@ export declare namespace XcodeInstanceCreateParams {
94
94
  };
95
95
  }
96
96
  interface Spec {
97
+ /** Inline instance role. Omit for the organization's compatibility default. */
98
+ role?: {
99
+ /** Asset permissions delegated by the creator. An empty array grants nothing. */
100
+ permissions: Array<string>;
101
+ };
97
102
  /**
98
103
  * ClientLocation takes precedence over ClientIP regardless of clue order. Without
99
104
  * either clue, routing uses Cloudflare visitor coordinates, then its client IP.
@@ -1 +1 @@
1
- {"version":3,"file":"xcode-instances.d.ts","sourceRoot":"","sources":["../src/resources/xcode-instances.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"xcode-instances.d.ts","sourceRoot":"","sources":["../src/resources/xcode-instances.ts"],"names":[],"mappings":"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"}
package/src/client.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { WorkloadCredentials } from './lib/workload-credentials';
1
2
  // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
3
 
3
4
  import type { RequestInit, RequestInfo, BodyInit } from './internal/builtin-types';
@@ -57,6 +58,7 @@ import {
57
58
  } from './resources/ios-instances';
58
59
  import { IosInstances } from './resources/ios-instances-helpers';
59
60
  import { SessionArtifact } from './resources/session-artifacts';
61
+ import { AccessToken, AccessTokenCreateParams, AccessTokens } from './resources/access-tokens';
60
62
  import { ScopedToken, ScopedTokenCreateParams, ScopedTokens } from './resources/scoped-tokens';
61
63
  import {
62
64
  XcodeInstance,
@@ -99,6 +101,8 @@ import {
99
101
  import { isEmptyObj } from './internal/utils/values';
100
102
 
101
103
  export interface ClientOptions {
104
+ /** Projected workload JWT file. Defaults to LIMRUN_WORKLOAD_IDENTITY_TOKEN_FILE. Null disables discovery. */
105
+ workloadTokenFile?: string | null;
102
106
  /**
103
107
  * Defaults to process.env['LIM_API_KEY'].
104
108
  */
@@ -193,6 +197,7 @@ export class Limrun {
193
197
  fetchOptions: MergedRequestInit | undefined;
194
198
 
195
199
  private fetch: Fetch;
200
+ private workloadCredentials?: WorkloadCredentials;
196
201
  #encoder: Opts.RequestEncoder;
197
202
  protected idempotencyHeader?: string;
198
203
  private _options: ClientOptions;
@@ -215,10 +220,15 @@ export class Limrun {
215
220
  apiKey = readEnv('LIM_API_KEY') ?? null,
216
221
  ...opts
217
222
  }: ClientOptions = {}) {
223
+ const tokenFile =
224
+ opts.workloadTokenFile === null ?
225
+ undefined
226
+ : opts.workloadTokenFile ?? readEnv('LIMRUN_WORKLOAD_IDENTITY_TOKEN_FILE');
218
227
  const options: ClientOptions = {
219
228
  apiKey,
220
229
  ...opts,
221
- baseURL: baseURL || `https://api.limrun.com`,
230
+ baseURL:
231
+ baseURL || (!apiKey && tokenFile ? readEnv('DIRECTOR_URL') : undefined) || `https://api.limrun.com`,
222
232
  };
223
233
 
224
234
  this.baseURL = options.baseURL!;
@@ -251,6 +261,13 @@ export class Limrun {
251
261
  this._options = options;
252
262
 
253
263
  this.apiKey = apiKey;
264
+ if (!apiKey && tokenFile)
265
+ this.workloadCredentials = new WorkloadCredentials(
266
+ this.baseURL,
267
+ tokenFile,
268
+ this.fetch,
269
+ this.fetchOptions,
270
+ );
254
271
  }
255
272
 
256
273
  /**
@@ -284,7 +301,7 @@ export class Limrun {
284
301
  }
285
302
 
286
303
  protected validateHeaders({ values, nulls }: NullableHeaders) {
287
- if (this.apiKey && values.get('authorization')) {
304
+ if ((this.apiKey || this.workloadCredentials) && values.get('authorization')) {
288
305
  return;
289
306
  }
290
307
  if (nulls.has('authorization')) {
@@ -298,6 +315,10 @@ export class Limrun {
298
315
 
299
316
  protected async authHeaders(opts: FinalRequestOptions): Promise<NullableHeaders | undefined> {
300
317
  if (this.apiKey == null) {
318
+ const explicit = buildHeaders([this._options.defaultHeaders, opts.headers]);
319
+ if (explicit.values.has('authorization') || explicit.nulls.has('authorization')) return undefined;
320
+ if (this.workloadCredentials)
321
+ return buildHeaders([{ Authorization: `Bearer ${await this.workloadCredentials.get()}` }]);
301
322
  return undefined;
302
323
  }
303
324
  return buildHeaders([{ Authorization: `Bearer ${this.apiKey}` }]);
@@ -850,6 +871,8 @@ export class Limrun {
850
871
  xcodeInstances: API.XcodeInstances = new API.XcodeInstances(this);
851
872
  gradleInstances: API.GradleInstances = new API.GradleInstances(this);
852
873
  analytics: API.Analytics = new API.Analytics(this);
874
+ accessTokens: API.AccessTokens = new API.AccessTokens(this);
875
+ /** @deprecated Use accessTokens. */
853
876
  scopedTokens: API.ScopedTokens = new API.ScopedTokens(this);
854
877
  }
855
878
 
@@ -859,6 +882,7 @@ Limrun.IosInstances = IosInstances;
859
882
  Limrun.XcodeInstances = XcodeInstances;
860
883
  Limrun.GradleInstances = GradleInstances;
861
884
  Limrun.Analytics = Analytics;
885
+ Limrun.AccessTokens = AccessTokens;
862
886
  Limrun.ScopedTokens = ScopedTokens;
863
887
 
864
888
  export declare namespace Limrun {
@@ -939,6 +963,9 @@ export declare namespace Limrun {
939
963
  };
940
964
 
941
965
  export {
966
+ AccessTokens as AccessTokens,
967
+ type AccessToken as AccessToken,
968
+ type AccessTokenCreateParams as AccessTokenCreateParams,
942
969
  ScopedTokens as ScopedTokens,
943
970
  type ScopedToken as ScopedToken,
944
971
  type ScopedTokenCreateParams as ScopedTokenCreateParams,
@@ -1,3 +1,4 @@
1
+ import type { AssetUpload } from './lib/asset-permissions';
1
2
  /**
2
3
  * Client for executing commands on limbuild server with streaming output.
3
4
  *
@@ -111,6 +112,7 @@ export type XcodeBuildExecRequest = {
111
112
  gitInit?: boolean;
112
113
  /** How xcodebuild stdout is processed before streaming. Defaults to 'xcbeautify'. */
113
114
  logProcessor?: 'xcbeautify' | 'none';
115
+ assetUpload?: AssetUpload;
114
116
  signedUploadUrl?: string;
115
117
  /**
116
118
  * ID of the Limrun asset signedUploadUrl targets, when it was minted from
@@ -208,6 +210,7 @@ export type GradleBuildExecRequest = {
208
210
  reactNative?: GradleReactNativeConfig;
209
211
  signing?: GradleSigningConfig;
210
212
  playstore?: GradlePlaystoreConfig;
213
+ assetUpload?: AssetUpload;
211
214
  signedUploadUrl?: string;
212
215
  webhook?: WebhookConfig;
213
216
  additionalMetadata?: {
package/src/index.ts CHANGED
@@ -226,3 +226,5 @@ export {
226
226
  UnprocessableEntityError,
227
227
  } from './core/error';
228
228
  export { isDirectInstanceHttpError, type DirectInstanceHttpError } from './internal/direct-instance-errors';
229
+
230
+ export { type AssetUpload, type BuildUpload } from './lib/asset-permissions';
package/src/ios-client.ts CHANGED
@@ -476,19 +476,77 @@ export type AppExitInfo = {
476
476
  */
477
477
  export type LaunchAppExitCallback = (logs: string[], info: AppExitInfo) => Promise<void> | void;
478
478
 
479
- /** Handle for an app watch registered via {@link InstanceClient.watchApp}. */
479
+ /** Handle for a watch registered via {@link InstanceClient.monitorApp} or {@link InstanceClient.watchApp}. */
480
480
  export type AppWatch = {
481
481
  /** Identifier of this watch on the server. */
482
482
  execId: string;
483
- /** Cancels the watch; the onExit callback will not be invoked afterwards. */
483
+ /** Cancels the watch; its callbacks will not be invoked afterwards. */
484
484
  stop: () => Promise<void>;
485
485
  };
486
486
 
487
+ /** What {@link InstanceClient.monitorApp} reports. Pass either callback or both. */
488
+ export type AppMonitorCallbacks = {
489
+ /** Called at every exit of any process of the app. Monitored apps report no logs. */
490
+ onExit?: LaunchAppExitCallback;
491
+ /** Called for each sample of a main-thread hang and once at its end. */
492
+ onHang?: AppHangCallback;
493
+ };
494
+
495
+ /**
496
+ * A sample of an app's main-thread hang, or the hang's end. The main thread is hung once it has
497
+ * not answered for 1 second. The simulator samples a hang 1, 3, 6 and 10 seconds in, then reports
498
+ * once more when the main thread answers or the app exits.
499
+ */
500
+ export type AppHangInfo = {
501
+ bundleId: string;
502
+ pid: number;
503
+ /** Numbers the process's hangs from 1. A hang's samples and its end share it. */
504
+ hangId: number;
505
+ /** `false` for a sample while the main thread is still hung, `true` once it answered or the app exited. */
506
+ ended: boolean;
507
+ /** The hang's age at this sample, or its whole length at the end. */
508
+ durationMs: number;
509
+ /**
510
+ * The main thread's run state at the sample: `running`, `waiting`, `uninterruptible`, `stopped`
511
+ * or `halted`. This and the fields below come with samples only, each left out when the app
512
+ * could not read it.
513
+ */
514
+ mainThreadState?: string;
515
+ /**
516
+ * CPU time the main thread used during the hang. Close to `durationMs` while `running` means the
517
+ * app kept its main thread busy; near zero while `waiting` means it waited, and `stackTrace`
518
+ * shows on what.
519
+ */
520
+ mainThreadCpuMs?: number;
521
+ /** The part of `mainThreadCpuMs` spent in the kernel, for example reading compressed memory back. */
522
+ mainThreadKernelMs?: number;
523
+ /** CPU time all of the app's threads used during the hang. */
524
+ appCpuMs?: number;
525
+ /** Pages the app read in from disk during the hang. */
526
+ pageIns?: number;
527
+ /** The app's memory footprint at the sample. */
528
+ footprintBytes?: number;
529
+ /**
530
+ * The main thread's frames, with symbol names for the app's own code, followed by the binary
531
+ * images they belong to, laid out like {@link AppCrashInfo.stackTrace}.
532
+ */
533
+ stackTrace?: string;
534
+ };
535
+
536
+ /** Called for each sample of a hang and once at its end. */
537
+ export type AppHangCallback = (info: AppHangInfo) => Promise<void> | void;
538
+
487
539
  type LaunchAppCommonOptions = {
488
540
  /** App environment variables. Requires relaunch; reserved keys and existing launch variables are ignored. */
489
541
  env?: Record<string, string>;
490
542
  /** Called once when the launched app exits, crashes, or is terminated. */
491
543
  onExit?: LaunchAppExitCallback;
544
+ /**
545
+ * Called for each sample of the launched process's main-thread hangs and once at each hang's
546
+ * end, until that process exits. Simulators that predate hang reports launch the app without
547
+ * them. See {@link InstanceClient.monitorApp}.
548
+ */
549
+ onHang?: AppHangCallback;
492
550
  };
493
551
 
494
552
  type StandardLaunchAppOptions = LaunchAppCommonOptions & {
@@ -804,6 +862,9 @@ export type InstanceClient = {
804
862
  * exit reason and crash details, the next time any process of the app exits. Watched apps
805
863
  * report no logs.
806
864
  *
865
+ * @deprecated Use {@link monitorApp}, which reports every exit and hang of the app until you
866
+ * stop it, including after relaunches. `watchApp` reports only the next exit and will be removed.
867
+ *
807
868
  * The app does not need to be running yet: if it starts later, it is picked up automatically.
808
869
  *
809
870
  * @param bundleId Bundle identifier of the app to watch
@@ -812,6 +873,23 @@ export type InstanceClient = {
812
873
  */
813
874
  watchApp: (bundleId: string, onExit: LaunchAppExitCallback) => Promise<AppWatch>;
814
875
 
876
+ /**
877
+ * Monitor an app without launching it through {@link launchApp}, for example an app a test
878
+ * runner relaunches or one opened with {@link openUrl}. `onExit` fires at every exit of any
879
+ * process of the app, with the reason and crash details. `onHang` fires for each sample of a
880
+ * main-thread hang (no answer for 1 second or more) and once at its end. The monitor outlives
881
+ * exits and relaunches until you stop it, and the app does not need to be running yet.
882
+ *
883
+ * Each app process reports at most 100 hangs. A simulator keeps its 8 newest monitors, so
884
+ * starting a ninth silently ends the oldest. Simulators that predate monitors reject the call
885
+ * after 10 seconds.
886
+ *
887
+ * @param bundleId Bundle identifier of the app to monitor
888
+ * @param callbacks `onExit`, `onHang`, or both
889
+ * @returns A handle whose `stop()` ends the monitor
890
+ */
891
+ monitorApp: (bundleId: string, callbacks: AppMonitorCallbacks) => Promise<AppWatch>;
892
+
815
893
  /**
816
894
  * Fetch the last N lines of app logs (combined stdout/stderr)
817
895
  * @param bundleId Bundle identifier of the app
@@ -1447,6 +1525,14 @@ type AppExitMessage = {
1447
1525
  logLineCount?: number;
1448
1526
  };
1449
1527
 
1528
+ /** appHang as the server sends it. */
1529
+ type AppHangMessage = {
1530
+ type: 'appHang';
1531
+ execId: string;
1532
+ bundleId: string;
1533
+ hang: Omit<AppHangInfo, 'bundleId'>;
1534
+ };
1535
+
1450
1536
  type ServerResponse = {
1451
1537
  recordingId?: string;
1452
1538
  state?: FoldState;
@@ -1693,8 +1779,8 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
1693
1779
  const simctlExecutions: Map<string, SimctlExecution> = new Map();
1694
1780
 
1695
1781
  // Server notifications are keyed by notification type and that type's
1696
- // identifier field. They intentionally survive transient WebSocket reconnects
1697
- // and are one-shot once a matching notification is processed.
1782
+ // identifier field. They intentionally survive transient WebSocket reconnects.
1783
+ // appExit handlers are one-shot; appHang handlers stay until their watch stops.
1698
1784
  type ServerNotificationHandler = (message: ServerResponse) => Promise<void>;
1699
1785
  const serverNotifications: Map<string, Map<string, ServerNotificationHandler>> = new Map();
1700
1786
 
@@ -1811,6 +1897,11 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
1811
1897
  return handler;
1812
1898
  };
1813
1899
 
1900
+ const getServerNotification = (
1901
+ type: string,
1902
+ notificationId: string,
1903
+ ): ServerNotificationHandler | undefined => serverNotifications.get(type)?.get(notificationId);
1904
+
1814
1905
  const deleteServerNotification = (type: string, notificationId: string): void => {
1815
1906
  const handlers = serverNotifications.get(type);
1816
1907
  if (!handlers) {
@@ -1943,7 +2034,9 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
1943
2034
  return true;
1944
2035
  }
1945
2036
 
1946
- const notification = takeServerNotification('appExit', execId);
2037
+ // Launches and watchApp end at the exit; monitors keep their handler until stopped.
2038
+ const notification =
2039
+ takeServerNotification('appExit', execId) ?? getServerNotification('monitorExit', execId);
1947
2040
  if (!notification) {
1948
2041
  logger.debug(`Received appExit for unknown or already handled execId: ${execId}`);
1949
2042
  return true;
@@ -1956,6 +2049,26 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
1956
2049
  return true;
1957
2050
  };
1958
2051
 
2052
+ const handleAppHang = (message: ServerResponse): boolean => {
2053
+ if (message.type !== 'appHang') {
2054
+ return false;
2055
+ }
2056
+ const { execId, bundleId, hang } = message as unknown as AppHangMessage;
2057
+ if (typeof execId !== 'string' || typeof bundleId !== 'string' || typeof hang !== 'object' || !hang) {
2058
+ logger.warn('Received malformed appHang message:', message);
2059
+ return true;
2060
+ }
2061
+ const notification = getServerNotification('appHang', execId);
2062
+ if (!notification) {
2063
+ logger.debug(`Received appHang for unknown or stopped execId: ${execId}`);
2064
+ return true;
2065
+ }
2066
+ void notification(message).catch((error) => {
2067
+ logger.error(`Error processing appHang notification for execId ${execId}:`, error);
2068
+ });
2069
+ return true;
2070
+ };
2071
+
1959
2072
  // Response handlers - transform raw responses to typed results
1960
2073
  const responseHandlers: Record<string, (msg: ServerResponse) => unknown> = {
1961
2074
  screenshotResult: (msg) => ({
@@ -1979,6 +2092,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
1979
2092
  launchAppResult: () => undefined,
1980
2093
  terminateAppResult: () => undefined,
1981
2094
  watchAppResult: () => undefined,
2095
+ monitorAppResult: () => undefined,
1982
2096
  unwatchAppResult: () => undefined,
1983
2097
  appLogTailResult: (msg) => msg.logs ?? '',
1984
2098
  listAppsResult: (msg) => JSON.parse(msg.apps || '[]') as InstalledApp[],
@@ -2048,7 +2162,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2048
2162
  return;
2049
2163
  }
2050
2164
 
2051
- if (handleAppExit(message)) {
2165
+ if (handleAppExit(message) || handleAppHang(message)) {
2052
2166
  return;
2053
2167
  }
2054
2168
 
@@ -2190,6 +2304,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2190
2304
  launchApp,
2191
2305
  terminateApp,
2192
2306
  watchApp,
2307
+ monitorApp,
2193
2308
  appLogTail,
2194
2309
  streamAppLog,
2195
2310
  streamSyslog,
@@ -2416,7 +2531,10 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2416
2531
  return sendRequest<void>('toggleKeyboard');
2417
2532
  };
2418
2533
 
2419
- const launchApp = (bundleId: string, modeOrOptions?: LaunchAppMode | LaunchAppOptions): Promise<void> => {
2534
+ const launchApp = async (
2535
+ bundleId: string,
2536
+ modeOrOptions?: LaunchAppMode | LaunchAppOptions,
2537
+ ): Promise<void> => {
2420
2538
  const launchOptions: LaunchAppOptions =
2421
2539
  typeof modeOrOptions === 'string' ? { mode: modeOrOptions } : modeOrOptions ?? {};
2422
2540
  const requestedMode =
@@ -2424,8 +2542,8 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2424
2542
  (modeOrOptions as { mode?: LaunchAppMode }).mode
2425
2543
  : launchOptions.mode;
2426
2544
  if (launchOptions.runtime && requestedMode === 'ForegroundIfRunning') {
2427
- return Promise.reject(
2428
- new Error('launchApp runtime launches require RelaunchIfRunning so runtime injection is applied.'),
2545
+ throw new Error(
2546
+ 'launchApp runtime launches require RelaunchIfRunning so runtime injection is applied.',
2429
2547
  );
2430
2548
  }
2431
2549
  const hasEnvironment = Object.keys(launchOptions.env ?? {}).length > 0;
@@ -2433,23 +2551,34 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2433
2551
  launchOptions.runtime ? 'RelaunchIfRunning' : (
2434
2552
  launchOptions.mode ?? (hasEnvironment ? 'RelaunchIfRunning' : undefined)
2435
2553
  );
2436
- const onExit = launchOptions.onExit;
2437
- const execId = onExit ? generateId() : undefined;
2438
- if (execId && onExit) {
2439
- registerServerNotification('appExit', execId, appExitHandler(execId, onExit));
2554
+ const { onExit, onHang } = launchOptions;
2555
+ const execId = onExit || onHang ? generateId() : undefined;
2556
+ if (execId) {
2557
+ const exitHandler = onExit ? appExitHandler(execId, onExit) : undefined;
2558
+ if (onHang) registerServerNotification('appHang', execId, appHangHandler(execId, onHang));
2559
+ // The launch's hang reports end with its exit.
2560
+ registerServerNotification('appExit', execId, async (message) => {
2561
+ deleteServerNotification('appHang', execId);
2562
+ await exitHandler?.(message);
2563
+ });
2440
2564
  }
2441
- return sendRequest<void>('launchApp', {
2442
- bundleId,
2443
- mode,
2444
- runtime: launchOptions.runtime,
2445
- env: launchOptions.env,
2446
- execId,
2447
- }).catch((error) => {
2565
+ try {
2566
+ await sendRequest<void>('launchApp', {
2567
+ bundleId,
2568
+ mode,
2569
+ runtime: launchOptions.runtime,
2570
+ env: launchOptions.env,
2571
+ execId,
2572
+ // Sent only with onHang so other launches stay byte-identical.
2573
+ hangs: onHang ? true : undefined,
2574
+ });
2575
+ } catch (error) {
2448
2576
  if (execId) {
2449
2577
  deleteServerNotification('appExit', execId);
2578
+ deleteServerNotification('appHang', execId);
2450
2579
  }
2451
2580
  throw error;
2452
- });
2581
+ }
2453
2582
  };
2454
2583
 
2455
2584
  /** Delivers one appExit to the callback, fetching the logs from servers that predate inline logs. */
@@ -2503,6 +2632,45 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2503
2632
  };
2504
2633
  };
2505
2634
 
2635
+ /** Delivers each appHang of a launch or hang watch to the callback. */
2636
+ const appHangHandler =
2637
+ (execId: string, onHang: AppHangCallback): ServerNotificationHandler =>
2638
+ async (response) => {
2639
+ const { hang, bundleId } = response as unknown as AppHangMessage;
2640
+ try {
2641
+ await onHang({ ...hang, bundleId });
2642
+ } catch (error) {
2643
+ logger.error(`Error in onHang callback for execId ${execId}:`, error);
2644
+ }
2645
+ };
2646
+
2647
+ const monitorApp = async (
2648
+ bundleId: string,
2649
+ { onExit, onHang }: AppMonitorCallbacks,
2650
+ ): Promise<AppWatch> => {
2651
+ const execId = generateId();
2652
+ const forget = () => {
2653
+ deleteServerNotification('monitorExit', execId);
2654
+ deleteServerNotification('appHang', execId);
2655
+ };
2656
+ if (onExit) registerServerNotification('monitorExit', execId, appExitHandler(execId, onExit));
2657
+ if (onHang) registerServerNotification('appHang', execId, appHangHandler(execId, onHang));
2658
+ try {
2659
+ // Simulators that predate monitors never answer, so the wait stays short.
2660
+ await sendRequest<void>('monitorApp', { bundleId, execId }, undefined, 10_000);
2661
+ } catch (error) {
2662
+ forget();
2663
+ throw error;
2664
+ }
2665
+ return {
2666
+ execId,
2667
+ stop: async () => {
2668
+ forget();
2669
+ await sendRequest<void>('unwatchApp', { execId });
2670
+ },
2671
+ };
2672
+ };
2673
+
2506
2674
  const terminateApp = (bundleId: string): Promise<void> => {
2507
2675
  return sendRequest<void>('terminateApp', { bundleId });
2508
2676
  };
@@ -0,0 +1,67 @@
1
+ /** Uploaded by the daemon after the build, using fresh workload credentials unless token is supplied. */
2
+ export interface AssetUpload {
3
+ target: string;
4
+ token?: string;
5
+ /** Asset expiry as a Go duration. Build uploads default to 336h. */
6
+ ttl?: string;
7
+ /** Metadata applied before the upload. Requires update permission. */
8
+ uploadOptions?: AssetUploadMetadata;
9
+ }
10
+
11
+ export interface AssetUploadMetadata {
12
+ displayName?: string;
13
+ bundleIdentifier?: string;
14
+ shortVersion?: string;
15
+ buildVersion?: string;
16
+ deeplink?: string;
17
+ }
18
+
19
+ export type BuildUpload =
20
+ | (AssetUpload & { assetName?: never; signedUploadUrl?: never })
21
+ | {
22
+ signedUploadUrl: string;
23
+ target?: never;
24
+ assetName?: never;
25
+ ttl?: never;
26
+ token?: never;
27
+ uploadOptions?: never;
28
+ }
29
+ | {
30
+ /** @deprecated Use a literal target such as "name/result.apk". Authorization now happens in the workload. */
31
+ assetName: string;
32
+ ttl?: string;
33
+ uploadOptions?: AssetUploadMetadata;
34
+ target?: never;
35
+ signedUploadUrl?: never;
36
+ token?: never;
37
+ };
38
+
39
+ export const DEFAULT_BUILD_ASSET_TTL = '336h';
40
+
41
+ /** Normalize compatibility names without requesting an asset URL on the client. */
42
+ export function normalizeAssetUpload(
43
+ upload: BuildUpload,
44
+ ): { assetUpload: AssetUpload; signedUploadUrl?: never } | { signedUploadUrl: string; assetUpload?: never } {
45
+ const modes = [upload.target, upload.assetName, upload.signedUploadUrl].filter((v) => v !== undefined);
46
+ if (modes.length !== 1) throw new Error('Provide exactly one upload target or signedUploadUrl');
47
+ if (upload.signedUploadUrl !== undefined) {
48
+ if (!upload.signedUploadUrl) throw new Error('signedUploadUrl must not be empty');
49
+ return { signedUploadUrl: upload.signedUploadUrl };
50
+ }
51
+ if (upload.assetName === '') throw new Error('assetName must not be empty');
52
+ const target = upload.target ?? `name/${upload.assetName!}`;
53
+ if (target.startsWith('name/')) {
54
+ if (target === 'name/') throw new Error('Name targets must include an exact name after name/');
55
+ } else if (!/^asset_[0-7][0-9a-hjkmnp-tv-z]{25}$/.test(target)) {
56
+ throw new Error('Upload target must be an asset ID or an exact name target');
57
+ }
58
+ if (upload.token !== undefined && !upload.token.trim()) throw new Error('Upload token must not be empty');
59
+ return {
60
+ assetUpload: {
61
+ target,
62
+ ttl: upload.ttl || DEFAULT_BUILD_ASSET_TTL,
63
+ ...(upload.token !== undefined && { token: upload.token }),
64
+ ...(upload.uploadOptions !== undefined && { uploadOptions: upload.uploadOptions }),
65
+ },
66
+ };
67
+ }
@@ -0,0 +1,44 @@
1
+ import type { MergedRequestInit } from '../internal/types';
2
+
3
+ /** Refreshes workload credentials without retaining the projected JWT. */
4
+ export class WorkloadCredentials {
5
+ private cached?: { token: string; expires: number };
6
+ private pending: Promise<string> | undefined;
7
+
8
+ constructor(
9
+ private endpoint: string,
10
+ private tokenFile: string,
11
+ private fetcher: typeof fetch,
12
+ private fetchOptions?: MergedRequestInit,
13
+ ) {}
14
+
15
+ async get(): Promise<string> {
16
+ if (this.cached && this.cached.expires - Date.now() > 60_000) return this.cached.token;
17
+ if (!this.pending)
18
+ this.pending = this.exchange().finally(() => {
19
+ this.pending = undefined;
20
+ });
21
+ return this.pending;
22
+ }
23
+
24
+ private async exchange(): Promise<string> {
25
+ const { readFile } = await import('node:fs/promises');
26
+ const jwt = (await readFile(this.tokenFile, 'utf8')).trim();
27
+ const response = await this.fetcher(`${this.endpoint.replace(/\/$/, '')}/v1/access_tokens`, {
28
+ ...(this.fetchOptions as RequestInit),
29
+ method: 'POST',
30
+ headers: { Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
31
+ body: JSON.stringify({}),
32
+ signal: AbortSignal.timeout(30_000),
33
+ redirect: 'error',
34
+ });
35
+ if (!response.ok) throw new Error(`Workload token exchange returned HTTP ${response.status}`);
36
+ const body = (await response.json()) as { token?: string; expiresAt: string };
37
+ if (!body.token) throw new Error('This instance has no delegated asset permissions');
38
+ const expires = Date.parse(body.expiresAt);
39
+ if (!Number.isFinite(expires) || expires <= Date.now())
40
+ throw new Error('Workload token exchange returned an invalid expiration');
41
+ this.cached = { token: body.token, expires };
42
+ return body.token;
43
+ }
44
+ }
@@ -0,0 +1,46 @@
1
+ import { APIResource } from '../core/resource';
2
+ import { APIPromise } from '../core/api-promise';
3
+ import { RequestOptions } from '../internal/request-options';
4
+
5
+ export class AccessTokens extends APIResource {
6
+ /** Mint a temporary credential whose permissions are covered by the caller. */
7
+ create(body: AccessTokenCreateParams, options?: RequestOptions): APIPromise<AccessToken> {
8
+ return this._client.post('/v1/access_tokens', { body, ...options });
9
+ }
10
+
11
+ /** Revoke a credential by the ID returned when it was minted. */
12
+ revoke(id: string, options?: RequestOptions): APIPromise<{ status: string }> {
13
+ return this._client.delete(`/v1/access_tokens/${encodeURIComponent(id)}`, options);
14
+ }
15
+ }
16
+
17
+ export interface AccessToken {
18
+ id: string;
19
+ token: string;
20
+ permissions: Array<string>;
21
+ /** @deprecated Use permissions. */
22
+ scopes: Array<string>;
23
+ expiresAt: string;
24
+ }
25
+
26
+ export interface AccessTokenCreateParams {
27
+ /**
28
+ * Permissions in the form `<resource>:<id|*>:<action>`, e.g. `device:*:install`,
29
+ * `asset:asset_01h455vb4pex5vsknk084sn02q:read` or `applerelay:*:connect`.
30
+ * Resource IDs are the customer-visible IDs returned by the API.
31
+ */
32
+ permissions?: Array<string>;
33
+
34
+ /** @deprecated Use permissions. If both are supplied, their permission sets must match. */
35
+ scopes?: Array<string>;
36
+
37
+ /**
38
+ * How long the token stays valid. Defaults to 3600 (1 hour), maximum is 14400 (4
39
+ * hours).
40
+ */
41
+ ttlSeconds?: number;
42
+ }
43
+
44
+ export declare namespace AccessTokens {
45
+ export { type AccessToken as AccessToken, type AccessTokenCreateParams as AccessTokenCreateParams };
46
+ }
@@ -190,6 +190,12 @@ export namespace AndroidInstanceCreateParams {
190
190
  }
191
191
 
192
192
  export interface Spec {
193
+ /** Inline instance role. Omit for the organization's compatibility default. */
194
+ role?: {
195
+ /** Asset permissions delegated by the creator. An empty array grants nothing. */
196
+ permissions: Array<string>;
197
+ };
198
+
193
199
  /**
194
200
  * ClientLocation takes precedence over ClientIP regardless of clue order. Without
195
201
  * either clue, routing uses Cloudflare visitor coordinates, then its client IP.