diff --git a/.changeset/poor-shirts-move.md b/.changeset/poor-shirts-move.md new file mode 100644 index 0000000000..a1dadfa60e --- /dev/null +++ b/.changeset/poor-shirts-move.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/core": patch +--- + +Fix realtime safari bug because of missing ReadableStream async iterable support diff --git a/.changeset/silent-trees-jump.md b/.changeset/silent-trees-jump.md new file mode 100644 index 0000000000..ca9796b1bf --- /dev/null +++ b/.changeset/silent-trees-jump.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/core": patch +--- + +Fix issue with dates in realtime not reflecting the current timezone diff --git a/packages/core/src/v3/apiClient/runStream.ts b/packages/core/src/v3/apiClient/runStream.ts index c1bb7c93ce..349dc80fd8 100644 --- a/packages/core/src/v3/apiClient/runStream.ts +++ b/packages/core/src/v3/apiClient/runStream.ts @@ -528,6 +528,9 @@ function apiStatusFromRunStatus(status: string): RunStatus { case "EXPIRED": { return "EXPIRED"; } + case "TIMED_OUT": { + return "TIMED_OUT"; + } default: { throw new Error(`Unknown status: ${status}`); } @@ -541,3 +544,69 @@ function safeParseJSON(data: string): unknown { return data; } } + +const isSafari = () => { + // Check if we're in a browser environment + if ( + typeof window !== "undefined" && + typeof navigator !== "undefined" && + typeof navigator.userAgent === "string" + ) { + return ( + /^((?!chrome|android).)*safari/i.test(navigator.userAgent) || + /iPad|iPhone|iPod/.test(navigator.userAgent) + ); + } + // If we're not in a browser environment, return false + return false; +}; + +/** + * A polyfill for `ReadableStream.protototype[Symbol.asyncIterator]`, + * aligning as closely as possible to the specification. + * + * @see https://streams.spec.whatwg.org/#rs-asynciterator + * @see https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#async_iteration + * + * This is needed for Safari: https://bugs.webkit.org/show_bug.cgi?id=194379 + * + * From https://gist.github.com/MattiasBuelens/496fc1d37adb50a733edd43853f2f60e + * + */ + +if (isSafari()) { + // @ts-expect-error + ReadableStream.prototype.values ??= function ({ preventCancel = false } = {}) { + const reader = this.getReader(); + return { + async next() { + try { + const result = await reader.read(); + if (result.done) { + reader.releaseLock(); + } + return result; + } catch (e) { + reader.releaseLock(); + throw e; + } + }, + async return(value: unknown) { + if (!preventCancel) { + const cancelPromise = reader.cancel(value); + reader.releaseLock(); + await cancelPromise; + } else { + reader.releaseLock(); + } + return { done: true, value }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; + }; + + // @ts-expect-error + ReadableStream.prototype[Symbol.asyncIterator] ??= ReadableStream.prototype.values; +} diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 55189d8140..a5ba40b75a 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -682,16 +682,26 @@ export const UpdateMetadataResponseBody = z.object({ export type UpdateMetadataResponseBody = z.infer; +const RawShapeDate = z + .string() + .transform((val) => `${val}Z`) + .pipe(z.coerce.date()); + +const RawOptionalShapeDate = z + .string() + .nullish() + .transform((val) => (val ? new Date(`${val}Z`) : val)); + export const SubscribeRunRawShape = z.object({ id: z.string(), idempotencyKey: z.string().nullish(), - createdAt: z.coerce.date(), - updatedAt: z.coerce.date(), - startedAt: z.coerce.date().nullish(), - delayUntil: z.coerce.date().nullish(), - queuedAt: z.coerce.date().nullish(), - expiredAt: z.coerce.date().nullish(), - completedAt: z.coerce.date().nullish(), + createdAt: RawShapeDate, + updatedAt: RawShapeDate, + startedAt: RawOptionalShapeDate, + delayUntil: RawOptionalShapeDate, + queuedAt: RawOptionalShapeDate, + expiredAt: RawOptionalShapeDate, + completedAt: RawOptionalShapeDate, taskIdentifier: z.string(), friendlyId: z.string(), number: z.number(),