Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 39 additions & 6 deletions integrations/intercom/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@ 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;
company: IntercomTypes.Company;
contact: IntercomTypes.Contact;
conversation: IntercomTypes.Conversation;
conversation_part: IntercomTypes.ConversationPart;
"conversation_part.tag": IntercomConversationPartTag;
event: IntercomTypes.DataEvent;
job: IntercomTypes.Jobs;
note: IntercomTypes.Note;
Expand All @@ -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 string> = 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<TPrefix extends keyof IntercomWebhookTypeMap> =
IntercomWebhookTypeMap[TPrefix] extends { type: infer TType }
? (TPrefix extends TType ? IntercomWebhookTypeMap[TPrefix] & { type: TPrefix }
: IntercomWebhookTypeMap[TPrefix])
: IntercomWebhookTypeMap[TPrefix] & { type: TPrefix };

type IntercomWebhookItem<TTopic extends string> = IntercomWebhookPrefix<TTopic> extends
infer TPrefix extends keyof IntercomWebhookTypeMap ? IntercomWebhookPayload<TPrefix>
: unknown;

export type IntercomEvent<TTopic extends string> = {
[K in TTopic]: {
type: "notification_event";
Expand All @@ -46,11 +83,7 @@ export type IntercomEvent<TTopic extends string> = {
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<K>;
};
};
}[TTopic];
Expand Down Expand Up @@ -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<T extends string>(
events: T[],
Expand Down
29 changes: 27 additions & 2 deletions integrations/intercom/runtime.types.test.ts
Original file line number Diff line number Diff line change
@@ -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<T>(_value: T): void {}

Expand All @@ -14,13 +14,19 @@ function verifyTopicBasedTyping(intercom: Intercom): void {
expectType<number>(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<IntercomTypes.Contact>(event.data.item);
expectType<"contact">(event.data.item.type);
break;
case "conversation.admin.closed":
expectType<IntercomTypes.Conversation>(event.data.item);
expectType<"conversation">(event.data.item.type);
break;
case "job.completed":
expectType<IntercomTypes.Jobs>(event.data.item);
expectType<"job">(event.data.item.type);
break;
}
});
Expand All @@ -29,6 +35,24 @@ function verifyTopicBasedTyping(intercom: Intercom): void {
expectType<"conversation_part">(event.data.item.type);
expectType<IntercomTypes.ConversationPart>(event.data.item);
});

intercom.onEvent(["conversation_part.tag.added"], (event) => {
expectType<IntercomConversationPartTag>(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<IntercomConversationPartTag>(event.data.item);
expectType<"conversation_part_tag">(event.data.item.type);
break;
case "conversation_part.replied":
expectType<IntercomTypes.ConversationPart>(event.data.item);
expectType<"conversation_part">(event.data.item.type);
break;
}
});
}

function verifyCustomTopicsRemainSupported(intercom: Intercom): void {
Expand All @@ -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<string>(event.id);
expectType<number>(event.first_sent_at);
}
Expand Down
6 changes: 5 additions & 1 deletion mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down