| 39 | const DEFAULT_REQUEST_TIMEOUT_MS = 10_000; |
| 40 | |
| 41 | export class AsyncTransport { |
| 42 | private readonly homeDir: string; |
| 43 | private readonly deviceId: string; |
| 44 | private readonly endpoint: string; |
| 45 | private readonly getAccessToken: (() => string | null | Promise<string | null>) | null; |
| 46 | private readonly fetchImpl: typeof fetch; |
| 47 | private readonly retryBackoffsMs: readonly number[]; |
| 48 | private readonly requestTimeoutMs: number; |
| 49 | private readonly sleepImpl: (ms: number, signal?: AbortSignal) => Promise<void>; |
| 50 | private readonly now: () => number; |
| 51 | |
| 52 | constructor(options: AsyncTransportOptions) { |
| 53 | this.homeDir = options.homeDir; |
| 54 | this.deviceId = options.deviceId; |
| 55 | this.endpoint = options.endpoint ?? TELEMETRY_ENDPOINT; |
| 56 | this.getAccessToken = options.getAccessToken ?? null; |
| 57 | this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis); |
| 58 | this.retryBackoffsMs = options.retryBackoffsMs ?? RETRY_BACKOFFS_MS; |
| 59 | this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS; |
| 60 | this.sleepImpl = options.sleep ?? abortableSleep; |
| 61 | this.now = options.now ?? Date.now; |
| 62 | } |
| 63 | |
| 64 | async send(events: readonly EnrichedTelemetryEvent[], signal?: AbortSignal): Promise<void> { |
| 65 | if (events.length === 0) return; |
| 66 | let savedToDisk = false; |
| 67 | const saveEventsToDisk = (): void => { |
| 68 | if (savedToDisk) return; |
| 69 | this.saveToDisk(events); |
| 70 | savedToDisk = true; |
| 71 | }; |
| 72 | if (signal?.aborted === true) { |
| 73 | saveEventsToDisk(); |
| 74 | throw abortError(); |
| 75 | } |
| 76 | |
| 77 | let payload: TelemetryPayload; |
| 78 | try { |
| 79 | payload = buildPayload(events, this.deviceId); |
| 80 | } catch { |
| 81 | return; |
| 82 | } |
| 83 | |
| 84 | try { |
| 85 | for (let attempt = 0; attempt <= this.retryBackoffsMs.length; attempt++) { |
| 86 | try { |
| 87 | await this.sendHttp(payload, signal); |
| 88 | return; |
| 89 | } catch (error) { |
| 90 | if (isSignalAborted(signal) || isAbortError(error)) { |
| 91 | saveEventsToDisk(); |
| 92 | throw error; |
| 93 | } |
| 94 | if (!(error instanceof TransientTelemetryError)) { |
| 95 | break; |
| 96 | } |
| 97 | const backoff = this.retryBackoffsMs[attempt]; |
| 98 | if (backoff === undefined) break; |
nothing calls this directly
no outgoing calls
no test coverage detected