@knocklabs/client 0.10.13 → 0.11.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cjs/clients/in-app-messages/channel-client.js +2 -0
  3. package/dist/cjs/clients/in-app-messages/channel-client.js.map +1 -0
  4. package/dist/cjs/clients/in-app-messages/message-client.js +2 -0
  5. package/dist/cjs/clients/in-app-messages/message-client.js.map +1 -0
  6. package/dist/cjs/clients/in-app-messages/socket-manager.js +2 -0
  7. package/dist/cjs/clients/in-app-messages/socket-manager.js.map +1 -0
  8. package/dist/cjs/clients/in-app-messages/store.js +2 -0
  9. package/dist/cjs/clients/in-app-messages/store.js.map +1 -0
  10. package/dist/cjs/clients/users/index.js +1 -1
  11. package/dist/cjs/clients/users/index.js.map +1 -1
  12. package/dist/cjs/index.js +1 -1
  13. package/dist/esm/clients/in-app-messages/channel-client.mjs +80 -0
  14. package/dist/esm/clients/in-app-messages/channel-client.mjs.map +1 -0
  15. package/dist/esm/clients/in-app-messages/message-client.mjs +153 -0
  16. package/dist/esm/clients/in-app-messages/message-client.mjs.map +1 -0
  17. package/dist/esm/clients/in-app-messages/socket-manager.mjs +83 -0
  18. package/dist/esm/clients/in-app-messages/socket-manager.mjs.map +1 -0
  19. package/dist/esm/clients/in-app-messages/store.mjs +11 -0
  20. package/dist/esm/clients/in-app-messages/store.mjs.map +1 -0
  21. package/dist/esm/clients/users/index.mjs +28 -20
  22. package/dist/esm/clients/users/index.mjs.map +1 -1
  23. package/dist/esm/index.mjs +11 -7
  24. package/dist/esm/index.mjs.map +1 -1
  25. package/dist/types/clients/in-app-messages/channel-client.d.ts +23 -0
  26. package/dist/types/clients/in-app-messages/channel-client.d.ts.map +1 -0
  27. package/dist/types/clients/in-app-messages/index.d.ts +4 -0
  28. package/dist/types/clients/in-app-messages/index.d.ts.map +1 -0
  29. package/dist/types/clients/in-app-messages/message-client.d.ts +52 -0
  30. package/dist/types/clients/in-app-messages/message-client.d.ts.map +1 -0
  31. package/dist/types/clients/in-app-messages/socket-manager.d.ts +27 -0
  32. package/dist/types/clients/in-app-messages/socket-manager.d.ts.map +1 -0
  33. package/dist/types/clients/in-app-messages/store.d.ts +6 -0
  34. package/dist/types/clients/in-app-messages/store.d.ts.map +1 -0
  35. package/dist/types/clients/in-app-messages/types.d.ts +52 -0
  36. package/dist/types/clients/in-app-messages/types.d.ts.map +1 -0
  37. package/dist/types/clients/preferences/interfaces.d.ts +1 -1
  38. package/dist/types/clients/preferences/interfaces.d.ts.map +1 -1
  39. package/dist/types/clients/users/index.d.ts +3 -1
  40. package/dist/types/clients/users/index.d.ts.map +1 -1
  41. package/dist/types/clients/users/interfaces.d.ts +6 -0
  42. package/dist/types/clients/users/interfaces.d.ts.map +1 -1
  43. package/dist/types/index.d.ts +1 -0
  44. package/dist/types/index.d.ts.map +1 -1
  45. package/package.json +11 -9
  46. package/src/clients/in-app-messages/channel-client.ts +129 -0
  47. package/src/clients/in-app-messages/index.ts +3 -0
  48. package/src/clients/in-app-messages/message-client.ts +271 -0
  49. package/src/clients/in-app-messages/socket-manager.ts +187 -0
  50. package/src/clients/in-app-messages/store.ts +15 -0
  51. package/src/clients/in-app-messages/types.ts +79 -0
  52. package/src/clients/preferences/interfaces.ts +1 -1
  53. package/src/clients/users/index.ts +19 -1
  54. package/src/clients/users/interfaces.ts +8 -0
  55. package/src/index.ts +1 -0
@@ -0,0 +1,271 @@
1
+ import { GenericData } from "@knocklabs/types";
2
+ import { nanoid } from "nanoid";
3
+
4
+ import Knock from "../../knock";
5
+ import { NetworkStatus, isRequestInFlight } from "../../networkStatus";
6
+ import { MessageEngagementStatus } from "../messages/interfaces";
7
+
8
+ import { InAppMessagesChannelClient } from "./channel-client";
9
+ import { SocketEventPayload, SocketEventType } from "./socket-manager";
10
+ import {
11
+ InAppMessage,
12
+ InAppMessagesClientOptions,
13
+ InAppMessagesResponse,
14
+ InAppMessagesStoreState,
15
+ } from "./types";
16
+
17
+ /**
18
+ * Manages realtime connection to in app messages service.
19
+ */
20
+ export class InAppMessagesClient {
21
+ private knock: Knock;
22
+
23
+ public queryKey: string;
24
+ public referenceId: string;
25
+ public unsub?: () => void;
26
+
27
+ constructor(
28
+ readonly channelClient: InAppMessagesChannelClient,
29
+ readonly messageType: string,
30
+ readonly defaultOptions: InAppMessagesClientOptions = {},
31
+ ) {
32
+ this.defaultOptions = {
33
+ ...channelClient.defaultOptions,
34
+ ...defaultOptions,
35
+ };
36
+ this.knock = channelClient.knock;
37
+ this.queryKey = this.buildQueryKey(this.defaultOptions);
38
+ this.referenceId = nanoid();
39
+
40
+ this.knock.log(`[IAM] Initialized a client for message ${messageType}`);
41
+ }
42
+
43
+ // ----------------------------------------------
44
+ // Data fetching
45
+ // ----------------------------------------------
46
+ async fetch<
47
+ TContent extends GenericData = GenericData,
48
+ TData extends GenericData = GenericData,
49
+ >(): Promise<
50
+ | {
51
+ status: "ok";
52
+ data: InAppMessagesResponse<TContent, TData>;
53
+ }
54
+ | {
55
+ status: "error";
56
+ error: Error;
57
+ }
58
+ | undefined
59
+ > {
60
+ const params = this.defaultOptions;
61
+
62
+ this.queryKey = this.buildQueryKey(params);
63
+
64
+ const queryState = this.channelClient.store.state.queries[
65
+ this.queryKey
66
+ ] ?? {
67
+ loading: false,
68
+ networkStatus: NetworkStatus.ready,
69
+ };
70
+ const networkStatus = queryState.networkStatus;
71
+
72
+ // If there's an existing request in flight, then do nothing
73
+ if (networkStatus && isRequestInFlight(networkStatus)) {
74
+ return;
75
+ }
76
+
77
+ // Set the loading type based on the request type it is
78
+ this.channelClient.setQueryStatus(this.queryKey, {
79
+ networkStatus: NetworkStatus.loading,
80
+ loading: true,
81
+ });
82
+
83
+ try {
84
+ const response = await this.knock.user.getInAppMessages<TContent, TData>({
85
+ channelId: this.channelClient.channelId,
86
+ messageType: this.messageType,
87
+ params,
88
+ });
89
+
90
+ this.channelClient.setQueryResponse(this.queryKey, response);
91
+
92
+ return { data: response, status: "ok" };
93
+ } catch (error) {
94
+ this.channelClient.setQueryStatus(this.queryKey, {
95
+ networkStatus: NetworkStatus.error,
96
+ loading: false,
97
+ });
98
+
99
+ return {
100
+ status: "error",
101
+ error: error as Error,
102
+ };
103
+ }
104
+ }
105
+
106
+ getQueryInfoSelector<
107
+ TContent extends GenericData = GenericData,
108
+ TData extends GenericData = GenericData,
109
+ >(
110
+ state: InAppMessagesStoreState,
111
+ ): {
112
+ messages: InAppMessage<TContent, TData>[];
113
+ loading: boolean;
114
+ networkStatus: NetworkStatus;
115
+ } {
116
+ const queryInfo = state.queries[this.queryKey];
117
+ const messageIds = queryInfo?.data?.messageIds ?? [];
118
+
119
+ const messages = messageIds.reduce<InAppMessage<TContent, TData>[]>(
120
+ (messages, messageId) => {
121
+ const message = state.messages[messageId];
122
+ if (message) {
123
+ messages.push(message as InAppMessage<TContent, TData>);
124
+ }
125
+ return messages;
126
+ },
127
+ [],
128
+ );
129
+
130
+ return {
131
+ messages,
132
+ networkStatus: queryInfo?.networkStatus ?? NetworkStatus.ready,
133
+ loading: queryInfo?.loading ?? false,
134
+ };
135
+ }
136
+
137
+ // ----------------------------------------------
138
+ // Message engagement
139
+ // ----------------------------------------------
140
+ async markAsSeen(itemOrItems: InAppMessage | InAppMessage[]) {
141
+ const itemIds = this.getItemIds(itemOrItems);
142
+
143
+ this.channelClient.setMessageAttrs(itemIds, {
144
+ seen_at: new Date().toISOString(),
145
+ });
146
+
147
+ return this.makeStatusUpdate(itemOrItems, "seen");
148
+ }
149
+
150
+ async markAsUnseen(itemOrItems: InAppMessage | InAppMessage[]) {
151
+ const itemIds = this.getItemIds(itemOrItems);
152
+
153
+ this.channelClient.setMessageAttrs(itemIds, {
154
+ seen_at: null,
155
+ });
156
+
157
+ return this.makeStatusUpdate(itemOrItems, "unseen");
158
+ }
159
+
160
+ async markAsRead(itemOrItems: InAppMessage | InAppMessage[]) {
161
+ const itemIds = this.getItemIds(itemOrItems);
162
+
163
+ this.channelClient.setMessageAttrs(itemIds, {
164
+ read_at: new Date().toISOString(),
165
+ });
166
+
167
+ return this.makeStatusUpdate(itemOrItems, "read");
168
+ }
169
+
170
+ async markAsUnread(itemOrItems: InAppMessage | InAppMessage[]) {
171
+ const itemIds = this.getItemIds(itemOrItems);
172
+
173
+ this.channelClient.setMessageAttrs(itemIds, {
174
+ read_at: null,
175
+ });
176
+
177
+ return this.makeStatusUpdate(itemOrItems, "unread");
178
+ }
179
+
180
+ async markAsInteracted(
181
+ itemOrItems: InAppMessage | InAppMessage[],
182
+ metadata?: Record<string, string>,
183
+ ) {
184
+ const now = new Date().toISOString();
185
+ const itemIds = this.getItemIds(itemOrItems);
186
+
187
+ this.channelClient.setMessageAttrs(itemIds, {
188
+ read_at: now,
189
+ interacted_at: now,
190
+ });
191
+
192
+ return this.makeStatusUpdate(itemOrItems, "interacted", metadata);
193
+ }
194
+
195
+ async markAsArchived(itemOrItems: InAppMessage | InAppMessage[]) {
196
+ const itemIds = this.getItemIds(itemOrItems);
197
+
198
+ this.channelClient.setMessageAttrs(itemIds, {
199
+ archived_at: new Date().toISOString(),
200
+ });
201
+
202
+ return this.makeStatusUpdate(itemOrItems, "archived");
203
+ }
204
+
205
+ async markAsUnarchived(itemOrItems: InAppMessage | InAppMessage[]) {
206
+ const itemIds = this.getItemIds(itemOrItems);
207
+
208
+ this.channelClient.setMessageAttrs(itemIds, {
209
+ archived_at: null,
210
+ });
211
+
212
+ return this.makeStatusUpdate(itemOrItems, "unarchived");
213
+ }
214
+
215
+ private async makeStatusUpdate(
216
+ itemOrItems: InAppMessage | InAppMessage[],
217
+ type: MessageEngagementStatus | "unread" | "unseen" | "unarchived",
218
+ metadata?: Record<string, string>,
219
+ ) {
220
+ // Always treat items as a batch to use the corresponding batch endpoint
221
+ const itemIds = this.getItemIds(itemOrItems);
222
+
223
+ const result = await this.knock.messages.batchUpdateStatuses(
224
+ itemIds,
225
+ type,
226
+ { metadata },
227
+ );
228
+
229
+ return result;
230
+ }
231
+
232
+ // ----------------------------------------------
233
+ // Helpers
234
+ // ----------------------------------------------
235
+ private buildQueryKey(params: GenericData): string {
236
+ const baseKey = `/v1/users/${this.knock.userId}/in-app-messages/${this.channelClient.channelId}/${this.messageType}`;
237
+ const paramsString = new URLSearchParams(params).toString();
238
+ return paramsString ? `${baseKey}?${paramsString}` : baseKey;
239
+ }
240
+
241
+ subscribe() {
242
+ this.unsub = this.channelClient.subscribe(this);
243
+ }
244
+
245
+ unsubscribe() {
246
+ this.channelClient.unsubscribe(this);
247
+ }
248
+
249
+ // This is a callback function that will be invoked when a new socket event
250
+ // relevant for this message client is received (and if subscribed).
251
+ async handleSocketEvent(payload: SocketEventPayload) {
252
+ switch (payload.event) {
253
+ case SocketEventType.MessageCreated:
254
+ // TODO(KNO-7169): Explore using an in-app message in the socket event
255
+ // directly instead of re-fetching.
256
+ return await this.fetch();
257
+
258
+ default:
259
+ throw new Error(`Unhandled socket event: ${payload.event}`);
260
+ }
261
+ }
262
+
263
+ socketChannelTopic() {
264
+ return `in_app:${this.messageType}:${this.channelClient.channelId}:${this.knock.userId}`;
265
+ }
266
+
267
+ private getItemIds(itemOrItems: InAppMessage | InAppMessage[]): string[] {
268
+ const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
269
+ return items.map((item) => item.id);
270
+ }
271
+ }
@@ -0,0 +1,187 @@
1
+ import { Store } from "@tanstack/store";
2
+ import { Channel, Socket } from "phoenix";
3
+
4
+ import { InAppMessagesClient } from "./message-client";
5
+ import { InAppMessage, InAppMessagesClientOptions } from "./types";
6
+
7
+ export enum SocketEventType {
8
+ MessageCreated = "message.created",
9
+ }
10
+
11
+ const SOCKET_EVENT_TYPES = [SocketEventType.MessageCreated];
12
+
13
+ type ClientQueryParams = InAppMessagesClientOptions;
14
+
15
+ // e.g. in_app:<message-type>:<channel_id>:<user_id>
16
+ type ChannelTopic = string;
17
+
18
+ // Unique reference id of an in-app message client
19
+ type ClientReferenceId = string;
20
+
21
+ type MessageCreatedEventPayload = {
22
+ event: SocketEventType.MessageCreated;
23
+ topic: string;
24
+ data: {
25
+ in_app_message: InAppMessage;
26
+ };
27
+ };
28
+
29
+ export type SocketEventPayload = MessageCreatedEventPayload;
30
+
31
+ // "attn" field contains a list of client reference ids that should be notified
32
+ // of a socket event.
33
+ type WithAttn<P> = P & { attn: Array<ClientReferenceId> };
34
+
35
+ type InAppMessageSocketInbox = Record<ClientReferenceId, SocketEventPayload>;
36
+
37
+ /*
38
+ * Manages socket subscriptions for in-app messages, allowing multiple in-app
39
+ * message clients or components to listen for real time updates from the socket
40
+ * API via a single socket connection. It's expected to be instantiated once
41
+ * per an in-app channel.
42
+ */
43
+ export class InAppMessageSocketManager {
44
+ // Mapping of live channels by topic. Note, there can be one or more in-app
45
+ // message client(s) that can subscribe.
46
+ private channels: Record<ChannelTopic, Channel>;
47
+
48
+ // Mapping of query params for each in-app message client, partitioned by its
49
+ // reference id, and grouped by channel topic. It's a double nested object
50
+ // that looks like:
51
+ // {
52
+ // "in_app:card:...": {
53
+ // "ref-1": {
54
+ // "workflow_key": "foo",
55
+ // },
56
+ // "ref-2": {
57
+ // "workflow_key": "bar",
58
+ // },
59
+ // },
60
+ // "in_app:banner:...": {
61
+ // "ref-3": {
62
+ // "workflow_key": "baz",
63
+ // },
64
+ // }
65
+ // }
66
+ //
67
+ // Each time a new in-app message client joins a channel, we send all cumulated
68
+ // params such that the socket API can apply filtering rules and figure out
69
+ // which in-app message clients should be notified basd on reference ids in
70
+ // "attn" field of the event payload when sending out an event.
71
+ private params: Record<
72
+ ChannelTopic,
73
+ Record<ClientReferenceId, ClientQueryParams>
74
+ >;
75
+
76
+ // A reactive store that captures a new socket event, that notifies any in-app
77
+ // message clients that have subscribed.
78
+ private inbox: Store<
79
+ InAppMessageSocketInbox,
80
+ (cb: InAppMessageSocketInbox) => InAppMessageSocketInbox
81
+ >;
82
+
83
+ constructor(readonly socket: Socket) {
84
+ this.channels = {};
85
+ this.params = {};
86
+ this.inbox = new Store<InAppMessageSocketInbox>({});
87
+ }
88
+
89
+ join(iamClient: InAppMessagesClient) {
90
+ const topic = iamClient.socketChannelTopic();
91
+ const referenceId = iamClient.referenceId;
92
+ const params = iamClient.defaultOptions;
93
+
94
+ // Ensure a live socket connection if not yet connected.
95
+ if (!this.socket.isConnected()) {
96
+ this.socket.connect();
97
+ }
98
+
99
+ // If a new in-app message client joins, or has updated query params then
100
+ // track the updated params and (re)join with the latest query params.
101
+ // Note, each time we send combined params of all in-app message clients that
102
+ // have subscribed for a given message type (and an in-app channel and a
103
+ // user), grouped by client's reference id.
104
+ if (!this.params[topic]) {
105
+ this.params[topic] = {};
106
+ }
107
+
108
+ const maybeParams = this.params[topic][referenceId];
109
+ const hasNewOrUpdatedParams =
110
+ !maybeParams || JSON.stringify(maybeParams) !== JSON.stringify(params);
111
+
112
+ if (hasNewOrUpdatedParams) {
113
+ // Tracks all subscribed client's params by its reference id and by topic.
114
+ this.params[topic] = { ...this.params[topic], [referenceId]: params };
115
+ }
116
+
117
+ if (!this.channels[topic] || hasNewOrUpdatedParams) {
118
+ const newChannel = this.socket.channel(topic, this.params[topic]);
119
+ for (const eventType of SOCKET_EVENT_TYPES) {
120
+ newChannel.on(eventType, (payload) => this.setInbox(payload));
121
+ }
122
+ // Tracks live channels by channel topic.
123
+ this.channels[topic] = newChannel;
124
+ }
125
+
126
+ const channel = this.channels[topic];
127
+
128
+ // Join the channel if not already joined or joining or leaving.
129
+ if (["closed", "errored"].includes(channel.state)) {
130
+ channel.join();
131
+ }
132
+
133
+ // Let the in-app message client subscribe to the "inbox", so it can be
134
+ // notified when there's a new socket event that is relevant for the client.
135
+ const unsub = this.inbox.subscribe(() => {
136
+ const payload = this.inbox.state[referenceId];
137
+ if (!payload) return;
138
+
139
+ iamClient.handleSocketEvent(payload);
140
+ });
141
+
142
+ return unsub;
143
+ }
144
+
145
+ leave(iamClient: InAppMessagesClient) {
146
+ if (iamClient.unsub) {
147
+ iamClient.unsub();
148
+ }
149
+
150
+ const topic = iamClient.socketChannelTopic();
151
+ const referenceId = iamClient.referenceId;
152
+
153
+ const partitionedParams = { ...this.params };
154
+ const paramsForTopic = partitionedParams[topic] || {};
155
+ const paramsForReferenceClient = paramsForTopic[referenceId];
156
+
157
+ if (paramsForReferenceClient) {
158
+ delete paramsForTopic[referenceId];
159
+ }
160
+
161
+ const channels = { ...this.channels };
162
+ const channelForTopic = channels[topic];
163
+ if (channelForTopic && Object.keys(paramsForTopic).length === 0) {
164
+ for (const eventType of SOCKET_EVENT_TYPES) {
165
+ channelForTopic.off(eventType);
166
+ }
167
+ channelForTopic.leave();
168
+ delete channels[topic];
169
+ }
170
+
171
+ this.params = partitionedParams;
172
+ this.channels = channels;
173
+ }
174
+
175
+ private setInbox(payload: WithAttn<SocketEventPayload>) {
176
+ const { attn, ...rest } = payload;
177
+
178
+ // Set the incoming socket event into the inbox, keyed by relevant client
179
+ // reference ids provided by the server (via attn field), so we can notify
180
+ // only the clients that need to be notified.
181
+ this.inbox.setState(() =>
182
+ attn.reduce((acc, referenceId) => {
183
+ return { ...acc, [referenceId]: rest };
184
+ }, {}),
185
+ );
186
+ }
187
+ }
@@ -0,0 +1,15 @@
1
+ import { Store } from "@tanstack/store";
2
+
3
+ import { InAppMessagesStoreState } from "./types";
4
+
5
+ export type InAppMessagesStore = Store<
6
+ InAppMessagesStoreState,
7
+ (cb: InAppMessagesStoreState) => InAppMessagesStoreState
8
+ >;
9
+
10
+ export function createStore() {
11
+ return new Store<InAppMessagesStoreState>({
12
+ messages: {},
13
+ queries: {},
14
+ });
15
+ }
@@ -0,0 +1,79 @@
1
+ import { GenericData, PageInfo, Tenant } from "@knocklabs/types";
2
+
3
+ import { NetworkStatus } from "../../networkStatus";
4
+ import { NotificationSource } from "../messages/interfaces";
5
+
6
+ export interface InAppMessage<
7
+ TContent extends GenericData = GenericData,
8
+ TData extends GenericData = GenericData,
9
+ TTenantProperties = GenericData,
10
+ > {
11
+ __cursor: string;
12
+ id: string;
13
+ message_type: string;
14
+ schema_variant: string;
15
+ schema_version: string;
16
+ content: TContent;
17
+ data: TData | null;
18
+ inserted_at: string;
19
+ updated_at: string;
20
+ seen_at: string | null;
21
+ read_at: string | null;
22
+ interacted_at: string | null;
23
+ archived_at: string | null;
24
+ link_clicked_at: string | null;
25
+ source: NotificationSource;
26
+ tenant: Tenant<TTenantProperties>;
27
+ }
28
+
29
+ export interface InAppMessagesResponse<
30
+ TContent extends GenericData = GenericData,
31
+ TData extends GenericData = GenericData,
32
+ > {
33
+ entries: InAppMessage<TContent, TData>[];
34
+ pageInfo: PageInfo;
35
+ }
36
+
37
+ export interface InAppMessagesQueryInfo {
38
+ networkStatus: NetworkStatus;
39
+ loading: boolean;
40
+ data?: {
41
+ messageIds: string[];
42
+ pageInfo: PageInfo;
43
+ };
44
+ }
45
+
46
+ export interface InAppMessagesStoreState {
47
+ messages: Record<string, InAppMessage>;
48
+ queries: Record<string, InAppMessagesQueryInfo>;
49
+ }
50
+
51
+ export type InAppMessageEngagementStatus =
52
+ | "read"
53
+ | "unread"
54
+ | "seen"
55
+ | "unseen"
56
+ | "link_clicked"
57
+ | "link_unclicked"
58
+ | "interacted"
59
+ | "uninteracted";
60
+
61
+ export interface InAppMessagesClientOptions {
62
+ order?: "asc" | "desc";
63
+ before?: string;
64
+ after?: string;
65
+ page_size?: number;
66
+ engagement_status?: InAppMessageEngagementStatus[];
67
+ // Optionally scope all requests to a particular tenant or tenants
68
+ tenant_id?: string | string[];
69
+ // Optionally scope to notifications from the given workflow or workflows
70
+ workflow_key?: string | string[];
71
+ // Optionally scope to notifications with any of the categories provided
72
+ workflow_categories?: string[];
73
+ // Optionally scope to a given archived status (defaults to `exclude`)
74
+ archived?: "include" | "exclude" | "only";
75
+ // Optionally scope all notifications that contain this argument as part of their trigger payload
76
+ // TODO(KNO-7140): This currently does not work because the API expects this
77
+ // to be a json string.
78
+ trigger_data?: GenericData;
79
+ }
@@ -1,7 +1,7 @@
1
1
  import { ChannelType } from "@knocklabs/types";
2
2
 
3
3
  export type ChannelTypePreferences = {
4
- [K in ChannelType]?: boolean;
4
+ [_K in ChannelType]?: boolean;
5
5
  };
6
6
 
7
7
  export type WorkflowPreferenceSetting =
@@ -3,6 +3,7 @@ import { GenericData } from "@knocklabs/types";
3
3
  import { ApiResponse } from "../../api";
4
4
  import { ChannelData, User } from "../../interfaces";
5
5
  import Knock from "../../knock";
6
+ import { InAppMessagesResponse } from "../in-app-messages";
6
7
  import {
7
8
  GetPreferencesOptions,
8
9
  PreferenceOptions,
@@ -10,7 +11,11 @@ import {
10
11
  SetPreferencesProperties,
11
12
  } from "../preferences/interfaces";
12
13
 
13
- import { GetChannelDataInput, SetChannelDataInput } from "./interfaces";
14
+ import {
15
+ GetChannelDataInput,
16
+ GetInAppMessagesInput,
17
+ SetChannelDataInput,
18
+ } from "./interfaces";
14
19
 
15
20
  const DEFAULT_PREFERENCE_SET_ID = "default";
16
21
 
@@ -100,6 +105,19 @@ class UserClient {
100
105
  return this.handleResponse<ChannelData<T>>(result);
101
106
  }
102
107
 
108
+ async getInAppMessages<
109
+ TContent extends GenericData = GenericData,
110
+ TData extends GenericData = GenericData,
111
+ >({ channelId, messageType, params }: GetInAppMessagesInput) {
112
+ const result = await this.instance.client().makeRequest({
113
+ method: "GET",
114
+ url: `/v1/users/${this.instance.userId}/in-app-messages/${channelId}/${messageType}`,
115
+ params,
116
+ });
117
+
118
+ return this.handleResponse<InAppMessagesResponse<TContent, TData>>(result);
119
+ }
120
+
103
121
  private handleResponse<T>(response: ApiResponse) {
104
122
  if (response.statusCode === "error") {
105
123
  throw new Error(response.error || response.body);
@@ -1,5 +1,7 @@
1
1
  import { GenericData } from "@knocklabs/types";
2
2
 
3
+ import { InAppMessagesClientOptions } from "../in-app-messages/types";
4
+
3
5
  export interface SetChannelDataInput {
4
6
  channelId: string;
5
7
  channelData: GenericData;
@@ -8,3 +10,9 @@ export interface SetChannelDataInput {
8
10
  export interface GetChannelDataInput {
9
11
  channelId: string;
10
12
  }
13
+
14
+ export interface GetInAppMessagesInput {
15
+ channelId: string;
16
+ messageType: string;
17
+ params: InAppMessagesClientOptions;
18
+ }
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export * from "./clients/slack/interfaces";
12
12
  export * from "./clients/users";
13
13
  export * from "./clients/users/interfaces";
14
14
  export * from "./clients/messages";
15
+ export * from "./clients/in-app-messages";
15
16
  export * from "./clients/messages/interfaces";
16
17
  export * from "./networkStatus";
17
18