diff --git a/packages/signals/README.md b/packages/signals/README.md index 6219ff2..7739f5a 100644 --- a/packages/signals/README.md +++ b/packages/signals/README.md @@ -74,7 +74,7 @@ The person an event belongs to always comes from the token: Didit overwrites any | `validation` | The event failed validation; `error.errors` has Didit's per-field details | | `dropped` | The queue was full and the event was dropped before it was sent | -`submitEvent` itself throws a `DiditEventError` only for a missing token (`invalid_token`), a missing `category` or `actionType` (`validation`), or an environment without `fetch` (`network`). +`submitEvent` itself throws a `DiditEventError` only for a missing token (`invalid_token`), a missing `category` or `actionType` or an unusable `occurredAt` Date (`validation`), or an environment without `fetch` (`network`). ## API diff --git a/packages/signals/src/__tests__/events.test.ts b/packages/signals/src/__tests__/events.test.ts index 55696fe..c1d3665 100644 --- a/packages/signals/src/__tests__/events.test.ts +++ b/packages/signals/src/__tests__/events.test.ts @@ -516,6 +516,12 @@ describe("submitEvent input", () => { ); }); + it("refuses an unusable occurredAt with a typed validation error", () => { + expect(() => + submitEvent({ monitoringToken: "valid", event: { category: "auth", actionType: "login", occurredAt: new Date("not a date") } }) + ).toThrow(expect.objectContaining({ name: "DiditEventError", type: "validation" })); + }); + it("returns the integrator's event id, or a new one per event", () => { const event: DiditEvent = { category: "auth", actionType: "login" }; diff --git a/packages/signals/src/events.ts b/packages/signals/src/events.ts index 82d55f3..18bdf15 100644 --- a/packages/signals/src/events.ts +++ b/packages/signals/src/events.ts @@ -434,6 +434,9 @@ export function submitEvent(options: SubmitEventOptions): string { if (!isRecord(event) || !event.category || typeof event.actionType !== "string" || !event.actionType.trim()) { throw new DiditEventError("validation", "event.category and event.actionType are required."); } + if (event.occurredAt instanceof Date && Number.isNaN(event.occurredAt.getTime())) { + throw new DiditEventError("validation", "event.occurredAt must be a valid date."); + } if (typeof fetch !== "function") { throw new DiditEventError("network", "fetch is not available in this environment."); } diff --git a/packages/signals/src/types.ts b/packages/signals/src/types.ts index 3144d09..3aedccb 100644 --- a/packages/signals/src/types.ts +++ b/packages/signals/src/types.ts @@ -71,7 +71,7 @@ export interface DiditEvent { * same content is a harmless replay; with different content it is rejected. */ eventId?: string; - /** When it happened. @default the time submitEvent was called */ + /** When it happened. An invalid Date is refused with a "validation" error. @default the time submitEvent was called */ occurredAt?: Date | string; /** IANA time zone of the end user. @default the browser's time zone */ timeZone?: string;