diff --git a/integrations/intercom/runtime.ts b/integrations/intercom/runtime.ts index fb2fbd7..5c284c9 100644 --- a/integrations/intercom/runtime.ts +++ b/integrations/intercom/runtime.ts @@ -19,6 +19,13 @@ export interface IntercomTriggerOptions extends CommonTriggerOptions { workspaceId?: string; } +export interface IntercomConversationPartTag { + type: "conversation_part_tag"; + created_at: number; + conversation: IntercomTypes.Conversation; + tag: IntercomTypes.Tag; +} + interface IntercomWebhookTypeMap { admin: IntercomTypes.Admin; article: IntercomTypes.Article; @@ -26,6 +33,7 @@ interface IntercomWebhookTypeMap { contact: IntercomTypes.Contact; conversation: IntercomTypes.Conversation; conversation_part: IntercomTypes.ConversationPart; + "conversation_part.tag": IntercomConversationPartTag; event: IntercomTypes.DataEvent; job: IntercomTypes.Jobs; note: IntercomTypes.Note; @@ -36,6 +44,35 @@ interface IntercomWebhookTypeMap { visitor: IntercomTypes.Visitor; } +/** Extracts the longest prefix of TTopic that matches a key in + * IntercomWebhookTypeMap. */ +type IntercomWebhookPrefix = TTopic extends + `${infer Prefix}.${infer Subtopic}.${string}` + ? (`${Prefix}.${Subtopic}` extends keyof IntercomWebhookTypeMap ? `${Prefix}.${Subtopic}` + : Prefix extends keyof IntercomWebhookTypeMap ? Prefix + : undefined) + : (TTopic extends `${infer Prefix}.${string}` + ? Prefix extends keyof IntercomWebhookTypeMap ? Prefix + : undefined + : undefined); + +/** + * Add {type: TPrefix} to the payload type unless it has a type property that + * TPrefix can't be cast to. This fixes the types in the type map that are + * missing the type property or have it set to something overly generic like + * `string` or `string | undefined`, but allows more specific types to remain + * intact. + */ +type IntercomWebhookPayload = + IntercomWebhookTypeMap[TPrefix] extends { type: infer TType } + ? (TPrefix extends TType ? IntercomWebhookTypeMap[TPrefix] & { type: TPrefix } + : IntercomWebhookTypeMap[TPrefix]) + : IntercomWebhookTypeMap[TPrefix] & { type: TPrefix }; + +type IntercomWebhookItem = IntercomWebhookPrefix extends + infer TPrefix extends keyof IntercomWebhookTypeMap ? IntercomWebhookPayload + : unknown; + export type IntercomEvent = { [K in TTopic]: { type: "notification_event"; @@ -46,11 +83,7 @@ export type IntercomEvent = { delivery_attempts: number; first_sent_at: number; data: { - item: K extends `${infer Prefix}.${string}` - ? Prefix extends keyof IntercomWebhookTypeMap - ? IntercomWebhookTypeMap[Prefix] & { type: Prefix } - : unknown - : unknown; + item: IntercomWebhookItem; }; }; }[TTopic]; @@ -144,7 +177,7 @@ export class Intercom { * }); * ``` * - * @see https://developers.intercom.com/intercom-api-reference/reference/webhook-topics - Full list of event types + * @see https://developers.intercom.com/docs/references/webhooks/webhook-models - Full list of event types */ onEvent( events: T[], diff --git a/integrations/intercom/runtime.types.test.ts b/integrations/intercom/runtime.types.test.ts index 1c129dc..a3c2ebd 100644 --- a/integrations/intercom/runtime.types.test.ts +++ b/integrations/intercom/runtime.types.test.ts @@ -1,6 +1,6 @@ import { assert } from "@std/assert"; import type { Intercom as IntercomTypes } from "intercom-client"; -import type { Intercom, IntercomEvent } from "./runtime.ts"; +import type { Intercom, IntercomConversationPartTag, IntercomEvent } from "./runtime.ts"; function expectType(_value: T): void {} @@ -14,13 +14,19 @@ function verifyTopicBasedTyping(intercom: Intercom): void { expectType(event.delivery_attempts); }); - intercom.onEvent(["contact.created", "conversation.admin.closed"], (event) => { + intercom.onEvent(["contact.created", "conversation.admin.closed", "job.completed"], (event) => { switch (event.topic) { case "contact.created": expectType(event.data.item); + expectType<"contact">(event.data.item.type); break; case "conversation.admin.closed": expectType(event.data.item); + expectType<"conversation">(event.data.item.type); + break; + case "job.completed": + expectType(event.data.item); + expectType<"job">(event.data.item.type); break; } }); @@ -29,6 +35,24 @@ function verifyTopicBasedTyping(intercom: Intercom): void { expectType<"conversation_part">(event.data.item.type); expectType(event.data.item); }); + + intercom.onEvent(["conversation_part.tag.added"], (event) => { + expectType(event.data.item); + expectType<"conversation_part_tag">(event.data.item.type); + }); + + intercom.onEvent(["conversation_part.tag.added", "conversation_part.replied"], (event) => { + switch (event.topic) { + case "conversation_part.tag.added": + expectType(event.data.item); + expectType<"conversation_part_tag">(event.data.item.type); + break; + case "conversation_part.replied": + expectType(event.data.item); + expectType<"conversation_part">(event.data.item.type); + break; + } + }); } function verifyCustomTopicsRemainSupported(intercom: Intercom): void { @@ -41,6 +65,7 @@ function verifyCustomTopicsRemainSupported(intercom: Intercom): void { function verifyEnvelopeType(event: IntercomEvent<"ticket.created">): void { expectType<"notification_event">(event.type); expectType<"ticket.created">(event.topic); + expectType<"ticket">(event.data.item.type); expectType(event.id); expectType(event.first_sent_at); } diff --git a/mod.ts b/mod.ts index 89ce6a2..e0f7689 100644 --- a/mod.ts +++ b/mod.ts @@ -51,7 +51,11 @@ export type { StreakTriggerOptions, } from "./integrations/streak/runtime.ts"; export type { StripeEvent, StripeTriggerOptions } from "./integrations/stripe/runtime.ts"; -export type { IntercomEvent, IntercomTriggerOptions } from "./integrations/intercom/runtime.ts"; +export type { + IntercomConversationPartTag, + IntercomEvent, + IntercomTriggerOptions, +} from "./integrations/intercom/runtime.ts"; export type { SlackCredentialFetcherOptions, SlackEventWebhook,