import { Client, createClient, createConfig, RequestResult, type TDataShape } from '@hey-api/client-fetch'; // @transform-path ../../../package.json import pkg from '../../package.json' with { type: 'json' }; import { HttpError } from '../errors/HttpError.js'; import { ApiError } from '../errors/ApiError.js'; import * as sdk from '../openapi-ts/sdk.gen.js'; import { CallResult, SuccessCallResult, ResponseTypes, KnownResponseTypes, TypedMeasurementRequest, TypedMeasurementResponse, TypedMeasurementResponses, FinishedMeasurementResponse, NoResponseTypes, Awaitable, } from '../types.js'; import { CreateMeasurementResponses, CreateMeasurementErrors, GetLimitsResponses, GetMeasurementErrors, ListProbesResponses, MeasurementResponse, MeasurementStatus, MeasurementType, type ClientOptions, } from '../openapi-ts/index.js'; import { wait } from '../utils.js'; type GlobalpingOptions = { auth?: string; userAgent?: string; throwApiErrors?: ThrowOnKnownErrors; timeout?: number; }; type GlobalpingRequestOptions = { auth?: string; userAgent?: string; signal?: AbortSignal; timeout?: number; }; export class Globalping { private readonly auth: string | undefined; private readonly client: Client; private readonly userAgent: string; private readonly throwApiErrors: ThrowApiErrors; private readonly timeout: number; constructor ({ auth, userAgent, throwApiErrors, timeout }: GlobalpingOptions = {}) { this.userAgent = userAgent ?? `globalping-typescript/${pkg.version} (https://github.com/jsdelivr/globalping-typescript)`; this.throwApiErrors = throwApiErrors ?? false as ThrowApiErrors; this.timeout = timeout ?? 30000; this.auth = auth; this.client = createClient(createConfig({ baseUrl: 'https://api.globalping.io', })); } /** * @see {@link sdk.createMeasurement} for the API docs */ createMeasurement (measurement: TypedMeasurementRequest) { return this.transformResult(this.request(sdk.createMeasurement, { body: measurement })); } /** * @see {@link sdk.getMeasurement} for the API docs */ async getMeasurement (id: string) { const result = await this.transformResult, GetMeasurementErrors>(this.request(sdk.getMeasurement, { path: { id } })); if (!result.ok) { return result; } return result; } /** * @see {@link sdk.getMeasurement} for the API docs */ async awaitMeasurement (id: string) { const getMeasurement = (eTag?: string | null) => { return this.request(sdk.getMeasurement, { path: { id }, headers: eTag ? { 'If-None-Match': eTag, } : {}, }); }; const start = Date.now(); let internalResult = await getMeasurement(); while (internalResult.data && internalResult.data.status === MeasurementStatus.IN_PROGRESS) { if (Date.now() - start > 60000) { throw new Error(`Timed out waiting for measurement ${id} to finish.`); } await wait(500); const newInternalResult = await getMeasurement(internalResult.response.headers.get('ETag')); if (newInternalResult.response.status !== 304) { internalResult = newInternalResult; } } const result = await this.transformResult, GetMeasurementErrors>(internalResult); if (!result.ok) { return result; } const data = result.data; Globalping.assertMeasurementFinished(data); return { ...result, data }; } /** * @see {@link sdk.listProbes} for the API docs */ async listProbes () { return this.transformResult(this.request(sdk.listProbes)); } /** * @see {@link sdk.getLimits} for the API docs */ async getLimits () { return this.transformResult(this.request(sdk.getLimits)); } private request (fn: () => RequestResult): RequestResult; private request> (fn: (options: O) => RequestResult, options?: O & GlobalpingRequestOptions): RequestResult; private request > (fn: (options: O) => RequestResult, options?: O & GlobalpingRequestOptions): RequestResult { const optionsWithDefaults = { auth: this.auth, timeout: this.timeout, userAgent: this.userAgent, ...(options || {}) as O, }; return fn({ client: this.client, fetch: (request: Request) => { const headers = new Headers(request.headers); headers.set('User-Agent', optionsWithDefaults.userAgent); if (optionsWithDefaults.auth) { headers.set('Authorization', `Bearer ${optionsWithDefaults.auth}`); } return fetch(new Request(request, { headers, signal: optionsWithDefaults.signal ?? AbortSignal.timeout(optionsWithDefaults.timeout), })); }, ...optionsWithDefaults, }); } private async transformResult (requestResult: Awaitable>>) { const { data, error, request, response } = await requestResult; if (error != null) { if (typeof error !== 'object' || !('error' in error)) { throw new HttpError(request, response); } if (this.throwApiErrors) { throw new ApiError(request, response, error); } return { ok: false, data: error, request, response } as ThrowApiErrors extends true ? never : CallResult; } if (data == null) { throw new Error('Unexpected undefined data'); } return { ok: true, data, request, response } as SuccessCallResult; } static assertHttpStatus (status: S, result: CallResult): asserts result is CallResult { if (!Globalping.isHttpStatus(status, result)) { throw new Error(`Expected HTTP status ${status}, got ${result.response.status}`); } } static assertMeasurementFinished (measurement: MeasurementResponse): asserts measurement is FinishedMeasurementResponse { if (!Globalping.isMeasurementFinished(measurement)) { throw new Error(`Expected measurement status ${MeasurementStatus.FINISHED}, got ${measurement.status}`); } } static assertMeasurementType (type: T, measurement: MeasurementResponse): asserts measurement is TypedMeasurementResponse { if (!Globalping.isMeasurementType(type, measurement)) { throw new Error(`Expected measurement type ${type}, got ${measurement.type}`); } } static isHttpStatus (status: S, result: CallResult): result is CallResult { return result.response.status === status; } static isMeasurementFinished (measurement: MeasurementResponse): measurement is FinishedMeasurementResponse { return measurement.status === MeasurementStatus.FINISHED; } static isMeasurementType (type: T, measurement: MeasurementResponse): measurement is TypedMeasurementResponse { return measurement.type === type; } } export default Globalping;