@doist/comms-sdk 2.0.2 → 3.0.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 (46) hide show
  1. package/dist/esm/utils/url-helpers.js +104 -16
  2. package/dist/types/utils/url-helpers.d.ts +41 -0
  3. package/package.json +5 -10
  4. package/dist/cjs/authentication.js +0 -256
  5. package/dist/cjs/clients/add-comment-helper.js +0 -43
  6. package/dist/cjs/clients/attachments-client.js +0 -71
  7. package/dist/cjs/clients/base-client.js +0 -33
  8. package/dist/cjs/clients/channels-client.js +0 -211
  9. package/dist/cjs/clients/comments-client.js +0 -175
  10. package/dist/cjs/clients/conversation-messages-client.js +0 -169
  11. package/dist/cjs/clients/conversations-client.js +0 -283
  12. package/dist/cjs/clients/groups-client.js +0 -172
  13. package/dist/cjs/clients/hooks-client.js +0 -52
  14. package/dist/cjs/clients/inbox-client.js +0 -184
  15. package/dist/cjs/clients/notify-audience.js +0 -45
  16. package/dist/cjs/clients/reactions-client.js +0 -97
  17. package/dist/cjs/clients/search-client.js +0 -138
  18. package/dist/cjs/clients/threads-client.js +0 -364
  19. package/dist/cjs/clients/users-client.js +0 -326
  20. package/dist/cjs/clients/workspace-users-client.js +0 -252
  21. package/dist/cjs/clients/workspaces-client.js +0 -174
  22. package/dist/cjs/comms-api.js +0 -70
  23. package/dist/cjs/consts/endpoints.js +0 -34
  24. package/dist/cjs/index.js +0 -53
  25. package/dist/cjs/migration.js +0 -96
  26. package/dist/cjs/package.json +0 -1
  27. package/dist/cjs/testUtils/msw-handlers.js +0 -51
  28. package/dist/cjs/testUtils/msw-setup.js +0 -35
  29. package/dist/cjs/testUtils/obsidian-fetch-adapter.js +0 -53
  30. package/dist/cjs/testUtils/test-defaults.js +0 -106
  31. package/dist/cjs/transport/fetch-with-retry.js +0 -146
  32. package/dist/cjs/transport/http-client.js +0 -56
  33. package/dist/cjs/transport/http-dispatcher.js +0 -190
  34. package/dist/cjs/types/api-version.js +0 -8
  35. package/dist/cjs/types/entities.js +0 -496
  36. package/dist/cjs/types/enums.js +0 -37
  37. package/dist/cjs/types/errors.js +0 -12
  38. package/dist/cjs/types/http.js +0 -4
  39. package/dist/cjs/types/index.js +0 -22
  40. package/dist/cjs/types/requests.js +0 -183
  41. package/dist/cjs/utils/case-conversion.js +0 -54
  42. package/dist/cjs/utils/index.js +0 -19
  43. package/dist/cjs/utils/multipart-upload.js +0 -202
  44. package/dist/cjs/utils/timestamp-conversion.js +0 -58
  45. package/dist/cjs/utils/url-helpers.js +0 -133
  46. package/dist/cjs/utils/uuidv7.js +0 -182
@@ -1,7 +1,34 @@
1
1
  /**
2
2
  * Helper functions for creating Comms permalinks (`https://comms.todoist.com/...`).
3
+ *
4
+ * Paths come from `@doist/sdk-kmp`, the shared implementation of Comms link
5
+ * routes, so they stay in step with the other Comms clients. The path builders
6
+ * are used rather than the URL builders so that `getFullCommsURL` can keep
7
+ * serving an arbitrary base URL.
3
8
  */
9
+ import { commsChannelPath, commsConversationPath, commsInboxThreadCommentPath, commsInboxThreadPath, commsMessagePath, commsSavedThreadCommentPath, commsSavedThreadPath, commsThreadCommentPath, commsThreadPath, parseCommsLink, } from '@doist/sdk-kmp/comms';
4
10
  const COMMS_BASE_URL = 'https://comms.todoist.com';
11
+ /**
12
+ * Builds a path, falling back to a locally built path when the link builder
13
+ * rejects one of the given ids. These helpers run while parsing API responses,
14
+ * so building a path must never fail the parse.
15
+ *
16
+ * Every error is caught, not just id rejection. The link builders expose no
17
+ * error type to narrow on, and a path is never important enough to fail a
18
+ * parse over, so any failure degrades to the locally built path.
19
+ *
20
+ * @param build Builds the path.
21
+ * @param fallback Builds the path without the shared link builder.
22
+ * @returns The built path, or the locally built path if an id was rejected.
23
+ */
24
+ function buildPath(build, fallback) {
25
+ try {
26
+ return build();
27
+ }
28
+ catch {
29
+ return fallback();
30
+ }
31
+ }
5
32
  /**
6
33
  * Builds a relative Comms URL based on the provided parameters
7
34
  * @param params - URL parameters including workspace, channel, conversation, thread, etc.
@@ -12,32 +39,33 @@ const COMMS_BASE_URL = 'https://comms.todoist.com';
12
39
  */
13
40
  export function getCommsURL(params) {
14
41
  const { workspaceId, channelId, conversationId, threadId, commentId, messageId, userId } = params;
15
- let url = `/${workspaceId}/`;
42
+ const workspace = String(workspaceId);
16
43
  if (channelId) {
17
- url += `ch/${channelId}/`;
44
+ if (threadId && commentId) {
45
+ return buildPath(() => commsThreadCommentPath(workspace, channelId, threadId, commentId), () => `/${workspace}/ch/${channelId}/t/${threadId}/c/${commentId}`);
46
+ }
18
47
  if (threadId) {
19
- url += `t/${threadId}/`;
20
- if (commentId) {
21
- url += `c/${commentId}`;
22
- }
48
+ return buildPath(() => commsThreadPath(workspace, channelId, threadId), () => `/${workspace}/ch/${channelId}/t/${threadId}/`);
23
49
  }
50
+ return buildPath(() => commsChannelPath(workspace, channelId), () => `/${workspace}/ch/${channelId}/`);
24
51
  }
25
- else if (threadId) {
26
- url += `inbox/t/${threadId}/`;
52
+ if (threadId) {
27
53
  if (commentId) {
28
- url += `c/${commentId}`;
54
+ return buildPath(() => commsInboxThreadCommentPath(workspace, threadId, commentId), () => `/${workspace}/inbox/t/${threadId}/c/${commentId}`);
29
55
  }
56
+ return buildPath(() => commsInboxThreadPath(workspace, threadId), () => `/${workspace}/inbox/t/${threadId}/`);
30
57
  }
31
- else if (conversationId) {
32
- url += `msg/${conversationId}/`;
58
+ if (conversationId) {
33
59
  if (messageId) {
34
- url += `m/${messageId}`;
60
+ return buildPath(() => commsMessagePath(workspace, conversationId, messageId), () => `/${workspace}/msg/${conversationId}/m/${messageId}`);
35
61
  }
62
+ return buildPath(() => commsConversationPath(workspace, conversationId), () => `/${workspace}/msg/${conversationId}/`);
36
63
  }
37
- else if (userId) {
38
- url += `people/u/${userId}`;
64
+ // The people view and the bare workspace root have no shared route.
65
+ if (userId) {
66
+ return `/${workspace}/people/u/${userId}`;
39
67
  }
40
- return url;
68
+ return `/${workspace}/`;
41
69
  }
42
70
  /**
43
71
  * Builds a full Comms URL (with protocol and hostname) based on the provided parameters
@@ -92,7 +120,13 @@ export function getSavedThreadsRootURL(workspaceId) {
92
120
  }
93
121
  /** Returns the URL for a saved thread. */
94
122
  export function getSavedThreadURL(params) {
95
- return `/${params.workspaceId}/saved/t/${params.threadId}`;
123
+ const workspace = String(params.workspaceId);
124
+ return buildPath(() => commsSavedThreadPath(workspace, params.threadId), () => `/${workspace}/saved/t/${params.threadId}`);
125
+ }
126
+ /** Returns the URL for a comment on a saved thread. */
127
+ export function getSavedThreadCommentURL(params) {
128
+ const workspace = String(params.workspaceId);
129
+ return buildPath(() => commsSavedThreadCommentPath(workspace, params.threadId, params.commentId), () => `/${workspace}/saved/t/${params.threadId}/c/${params.commentId}`);
96
130
  }
97
131
  /** Returns the URL for the search root. */
98
132
  export function getSearchRootURL(workspaceId) {
@@ -112,3 +146,57 @@ export function getSettingsURL(params) {
112
146
  export function getTeamMembersRootURL(workspaceId) {
113
147
  return `/${workspaceId}/people/u`;
114
148
  }
149
+ /** The kinds of Comms entity a URL can point at. */
150
+ export const COMMS_LINK_TYPES = [
151
+ 'channel',
152
+ 'thread',
153
+ 'thread_comment',
154
+ 'inbox_thread',
155
+ 'inbox_thread_comment',
156
+ 'saved_thread',
157
+ 'saved_thread_comment',
158
+ 'conversation',
159
+ 'message',
160
+ ];
161
+ /**
162
+ * Maps the link parser's type names onto the types this SDK exposes. A name
163
+ * that is missing here is one this SDK does not know about, and is treated as
164
+ * an unrecognised URL rather than surfaced as a value outside the union.
165
+ */
166
+ const LINK_TYPE_BY_PARSED_NAME = {
167
+ Channel: 'channel',
168
+ Thread: 'thread',
169
+ ThreadComment: 'thread_comment',
170
+ InboxThread: 'inbox_thread',
171
+ InboxThreadComment: 'inbox_thread_comment',
172
+ SavedThread: 'saved_thread',
173
+ SavedThreadComment: 'saved_thread_comment',
174
+ Conversation: 'conversation',
175
+ Message: 'message',
176
+ };
177
+ /**
178
+ * Parse a Comms URL into the entity it points at.
179
+ *
180
+ * @param url The URL to parse.
181
+ * @returns The entity the URL points at, or `null` if it is not a Comms URL.
182
+ */
183
+ export function parseCommsURL(url) {
184
+ const link = parseCommsLink(url);
185
+ if (!link) {
186
+ return null;
187
+ }
188
+ const type = LINK_TYPE_BY_PARSED_NAME[link.type.name];
189
+ if (!type) {
190
+ return null;
191
+ }
192
+ return {
193
+ type,
194
+ workspaceId: link.workspaceId,
195
+ channelId: link.channelId ?? null,
196
+ threadId: link.threadId ?? null,
197
+ commentId: link.commentId ?? null,
198
+ conversationId: link.conversationId ?? null,
199
+ messageId: link.messageId ?? null,
200
+ isQuoteReference: link.isQuoteReference,
201
+ };
202
+ }
@@ -1,5 +1,10 @@
1
1
  /**
2
2
  * Helper functions for creating Comms permalinks (`https://comms.todoist.com/...`).
3
+ *
4
+ * Paths come from `@doist/sdk-kmp`, the shared implementation of Comms link
5
+ * routes, so they stay in step with the other Comms clients. The path builders
6
+ * are used rather than the URL builders so that `getFullCommsURL` can keep
7
+ * serving an arbitrary base URL.
3
8
  */
4
9
  export type CommsURLParams = {
5
10
  workspaceId: number;
@@ -72,6 +77,12 @@ export declare function getSavedThreadURL(params: {
72
77
  workspaceId: number;
73
78
  threadId: string;
74
79
  }): string;
80
+ /** Returns the URL for a comment on a saved thread. */
81
+ export declare function getSavedThreadCommentURL(params: {
82
+ workspaceId: number;
83
+ threadId: string;
84
+ commentId: string;
85
+ }): string;
75
86
  /** Returns the URL for the search root. */
76
87
  export declare function getSearchRootURL(workspaceId: number): string;
77
88
  /** Returns the URL for a search with a query. */
@@ -86,3 +97,33 @@ export declare function getSettingsURL(params: {
86
97
  }): string;
87
98
  /** Returns the URL for the team members root. */
88
99
  export declare function getTeamMembersRootURL(workspaceId: number): string;
100
+ /** The kinds of Comms entity a URL can point at. */
101
+ export declare const COMMS_LINK_TYPES: readonly ["channel", "thread", "thread_comment", "inbox_thread", "inbox_thread_comment", "saved_thread", "saved_thread_comment", "conversation", "message"];
102
+ /** The kind of Comms entity a URL points at. */
103
+ export type CommsLinkType = (typeof COMMS_LINK_TYPES)[number];
104
+ /** The entity a Comms URL points at. */
105
+ export type CommsURLInfo = {
106
+ /** The kind of entity the URL points at. */
107
+ type: CommsLinkType;
108
+ /** The ID of the workspace the entity belongs to. */
109
+ workspaceId: string;
110
+ /** The ID of the channel, when the URL names one. */
111
+ channelId: string | null;
112
+ /** The ID of the thread, when the URL names one. */
113
+ threadId: string | null;
114
+ /** The ID of the comment, when the URL names one. */
115
+ commentId: string | null;
116
+ /** The ID of the conversation, when the URL names one. */
117
+ conversationId: string | null;
118
+ /** The ID of the message, when the URL names one. */
119
+ messageId: string | null;
120
+ /** Whether the URL points at a quoted reference rather than the entity itself. */
121
+ isQuoteReference: boolean;
122
+ };
123
+ /**
124
+ * Parse a Comms URL into the entity it points at.
125
+ *
126
+ * @param url The URL to parse.
127
+ * @returns The entity the URL points at, or `null` if it is not a Comms URL.
128
+ */
129
+ export declare function parseCommsURL(url: string): CommsURLInfo | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/comms-sdk",
3
- "version": "2.0.2",
3
+ "version": "3.0.0",
4
4
  "description": "A TypeScript wrapper for the Comms REST API.",
5
5
  "author": "Doist developers",
6
6
  "homepage": "https://doist.github.io/comms-sdk-typescript/",
@@ -15,14 +15,11 @@
15
15
  "node": ">=24",
16
16
  "npm": ">=11"
17
17
  },
18
- "main": "dist/cjs/index.js",
19
- "module": "dist/esm/index.js",
20
18
  "types": "dist/types/index.d.ts",
21
19
  "exports": {
22
20
  ".": {
23
21
  "types": "./dist/types/index.d.ts",
24
- "import": "./dist/esm/index.js",
25
- "require": "./dist/cjs/index.js"
22
+ "default": "./dist/esm/index.js"
26
23
  }
27
24
  },
28
25
  "sideEffects": false,
@@ -31,22 +28,21 @@
31
28
  "test": "vitest run",
32
29
  "test:watch": "vitest",
33
30
  "test:coverage": "vitest run --coverage",
34
- "build:cjs": "npx tsc -p tsconfig.cjs.json",
35
31
  "build:esm": "npx tsc -p tsconfig.esm.json",
36
32
  "build:fix-esm": "node scripts/fix-esm-imports.cjs",
37
33
  "build:fix-dts": "node scripts/fix-dts-imports.cjs",
38
- "build:post": "echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
39
- "build": "npm-run-all clean build:cjs build:esm build:fix-esm build:fix-dts build:post",
34
+ "build": "npm-run-all clean build:esm build:fix-esm build:fix-dts",
40
35
  "type-check": "npx tsc --noEmit",
41
36
  "check": "oxlint src && oxfmt --check",
42
37
  "check:fix": "oxlint src --fix && oxfmt",
43
- "attw": "attw --pack --ignore-rules fallback-condition false-esm",
38
+ "attw": "attw --pack --ignore-rules cjs-resolves-to-esm",
44
39
  "audit": "npm audit --audit-level=moderate",
45
40
  "integrity-checks": "npm-run-all clean check test build attw",
46
41
  "prepublishOnly": "npm run integrity-checks",
47
42
  "prepare": "npm run build"
48
43
  },
49
44
  "dependencies": {
45
+ "@doist/sdk-kmp": "0.2.3",
50
46
  "camelcase": "9.0.0",
51
47
  "ts-custom-error": "3.3.1",
52
48
  "undici": "7.29.0",
@@ -81,7 +77,6 @@
81
77
  "*.{ts,tsx,json,md,yml,yaml}": "oxfmt --no-error-on-unmatched-pattern"
82
78
  },
83
79
  "files": [
84
- "dist/cjs/**/*",
85
80
  "dist/esm/**/*",
86
81
  "dist/types/**/*",
87
82
  "!dist/**/*.test.js",
@@ -1,256 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TOKEN_ENDPOINT_AUTH_METHODS = exports.COMMS_SCOPES = void 0;
4
- exports.getAuthStateParameter = getAuthStateParameter;
5
- exports.getAuthorizationUrl = getAuthorizationUrl;
6
- exports.getAuthToken = getAuthToken;
7
- exports.refreshAuthToken = refreshAuthToken;
8
- exports.revokeAuthToken = revokeAuthToken;
9
- exports.registerClient = registerClient;
10
- const uuid_1 = require("uuid");
11
- const http_client_1 = require("./transport/http-client");
12
- const errors_1 = require("./types/errors");
13
- /**
14
- * Default origin of the Comms authorization server.
15
- *
16
- * The authorization server for Comms is Todoist (`todoist.com`), not the
17
- * resource server (`comms.todoist.com`). Tokens are issued by Todoist and
18
- * accepted by the Comms API. Paths match Todoist's published
19
- * `/.well-known/oauth-authorization-server` metadata.
20
- */
21
- const DEFAULT_AUTH_BASE_URL = 'https://todoist.com';
22
- function getAuthBaseUrl(baseUrl) {
23
- const base = baseUrl ?? DEFAULT_AUTH_BASE_URL;
24
- return base.endsWith('/') ? base.slice(0, -1) : base;
25
- }
26
- /**
27
- * OAuth scopes for the Comms API.
28
- *
29
- * @remarks
30
- * Request only the scopes your application needs:
31
- *
32
- * **User Scopes:**
33
- * - `user:read` - Access user's personal settings
34
- * - `user:write` - Access and update user's personal settings
35
- *
36
- * **Workspace Scopes:**
37
- * - `workspaces:read` - Access teams the user is part of
38
- * - `workspaces:write` - Access and update teams the user is part of
39
- *
40
- * **Channel Scopes:**
41
- * - `channels:read` - Access channels
42
- * - `channels:write` - Access and update channels
43
- * - `channels:remove` - Access, update, and delete channels
44
- *
45
- * **Thread Scopes:**
46
- * - `threads:read` - Access threads
47
- * - `threads:write` - Access and update threads
48
- * - `threads:remove` - Access, update, and delete threads
49
- *
50
- * **Comment Scopes:**
51
- * - `comments:read` - Access comments
52
- * - `comments:write` - Access and update comments
53
- * - `comments:remove` - Access, update, and delete comments
54
- *
55
- * **Group Scopes:**
56
- * - `groups:read` - Access groups
57
- * - `groups:write` - Access and update groups
58
- * - `groups:remove` - Access, update, and delete groups
59
- *
60
- * **Message Scopes:**
61
- * - `messages:read` - Access messages
62
- * - `messages:write` - Access and update messages
63
- * - `messages:remove` - Access, update, and delete messages
64
- *
65
- * **Reaction Scopes:**
66
- * - `reactions:read` - Access reactions
67
- * - `reactions:write` - Access and update reactions
68
- * - `reactions:remove` - Access, update, and delete reactions
69
- *
70
- * **Search Scopes:**
71
- * - `search:read` - Search
72
- *
73
- * **Attachment Scopes:**
74
- * - `attachments:read` - Access attachments
75
- * - `attachments:write` - Access and update attachments
76
- *
77
- * **Notification Scopes:**
78
- * - `notifications:read` - Read user's notifications settings
79
- * - `notifications:write` - Read and update user's notifications settings
80
- */
81
- exports.COMMS_SCOPES = [
82
- 'user:read',
83
- 'user:write',
84
- 'workspaces:read',
85
- 'workspaces:write',
86
- 'channels:read',
87
- 'channels:write',
88
- 'channels:remove',
89
- 'threads:read',
90
- 'threads:write',
91
- 'threads:remove',
92
- 'comments:read',
93
- 'comments:write',
94
- 'comments:remove',
95
- 'groups:read',
96
- 'groups:write',
97
- 'groups:remove',
98
- 'messages:read',
99
- 'messages:write',
100
- 'messages:remove',
101
- 'reactions:read',
102
- 'reactions:write',
103
- 'reactions:remove',
104
- 'search:read',
105
- 'attachments:read',
106
- 'attachments:write',
107
- 'notifications:read',
108
- 'notifications:write',
109
- ];
110
- /** Supported token endpoint authentication methods for dynamic client registration. */
111
- exports.TOKEN_ENDPOINT_AUTH_METHODS = [
112
- 'client_secret_post',
113
- 'client_secret_basic',
114
- 'none',
115
- ];
116
- function getAuthStateParameter() {
117
- return (0, uuid_1.v4)();
118
- }
119
- /**
120
- * Generates the authorization URL for the OAuth2 flow.
121
- *
122
- * The `clientId` can be either a traditional client ID string (e.g. from
123
- * {@link registerClient}) or an HTTPS URL pointing to a client metadata document,
124
- * as defined in {@link https://drafts.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/ RFC draft-ietf-oauth-client-id-metadata-document}.
125
- */
126
- function getAuthorizationUrl(clientId, scopes, state, redirectUri, baseUrl) {
127
- if (!scopes?.length) {
128
- throw new Error('At least one scope value is required.');
129
- }
130
- const authBaseUrl = `${getAuthBaseUrl(baseUrl)}/oauth`;
131
- const scope = scopes.join(' ');
132
- const params = new URLSearchParams({
133
- client_id: clientId,
134
- response_type: 'code',
135
- scope,
136
- state,
137
- });
138
- if (redirectUri) {
139
- params.append('redirect_uri', redirectUri);
140
- }
141
- return `${authBaseUrl}/authorize?${params.toString()}`;
142
- }
143
- async function getAuthToken(args, options) {
144
- const tokenUrl = `${getAuthBaseUrl(options?.baseUrl)}/oauth/access_token`;
145
- const payload = {
146
- clientId: args.clientId,
147
- clientSecret: args.clientSecret,
148
- code: args.code,
149
- grantType: 'authorization_code',
150
- ...(args.redirectUri && { redirectUri: args.redirectUri }),
151
- };
152
- const response = await (0, http_client_1.request)({
153
- httpMethod: 'POST',
154
- baseUri: tokenUrl,
155
- relativePath: '',
156
- payload,
157
- customFetch: options?.customFetch,
158
- });
159
- if (!(0, http_client_1.isSuccess)(response) || !response.data?.accessToken) {
160
- throw new errors_1.CommsRequestError('Authentication token exchange failed.', response.status, response.data);
161
- }
162
- return response.data;
163
- }
164
- /**
165
- * Exchanges a refresh token for a new access token using the OAuth2
166
- * `refresh_token` grant.
167
- *
168
- * @example
169
- * ```typescript
170
- * const { accessToken, refreshToken } = await refreshAuthToken({
171
- * clientId: 'client-id',
172
- * clientSecret: 'client-secret',
173
- * refreshToken: storedRefreshToken,
174
- * })
175
- * ```
176
- *
177
- * @returns The refreshed token. The response may include a new `refreshToken`
178
- * (rotated by the server); persist it in place of the previous one when present.
179
- * @throws {@link CommsRequestError} If the refresh fails
180
- */
181
- async function refreshAuthToken(args, options) {
182
- const tokenUrl = `${getAuthBaseUrl(options?.baseUrl)}/oauth/access_token`;
183
- const payload = {
184
- clientId: args.clientId,
185
- clientSecret: args.clientSecret,
186
- refreshToken: args.refreshToken,
187
- grantType: 'refresh_token',
188
- };
189
- const response = await (0, http_client_1.request)({
190
- httpMethod: 'POST',
191
- baseUri: tokenUrl,
192
- relativePath: '',
193
- payload,
194
- customFetch: options?.customFetch,
195
- });
196
- if (!(0, http_client_1.isSuccess)(response) || !response.data?.accessToken) {
197
- throw new errors_1.CommsRequestError('Authentication token refresh failed.', response.status, response.data);
198
- }
199
- return response.data;
200
- }
201
- async function revokeAuthToken(args, options) {
202
- const revokeUrl = `${getAuthBaseUrl(options?.baseUrl)}/api/v1/revoke`;
203
- const response = await (0, http_client_1.request)({
204
- httpMethod: 'POST',
205
- baseUri: revokeUrl,
206
- relativePath: '',
207
- payload: {
208
- clientId: args.clientId,
209
- clientSecret: args.clientSecret,
210
- token: args.accessToken,
211
- },
212
- customFetch: options?.customFetch,
213
- });
214
- return (0, http_client_1.isSuccess)(response);
215
- }
216
- /**
217
- * Registers a new OAuth client via Dynamic Client Registration (RFC 7591).
218
- *
219
- * @example
220
- * ```typescript
221
- * const client = await registerClient({
222
- * redirectUris: ['https://example.com/callback'],
223
- * clientName: 'My App',
224
- * scope: ['user:read', 'channels:read'],
225
- * })
226
- * // Use client.clientId and client.clientSecret for OAuth flows
227
- * ```
228
- *
229
- * @returns The registered client details
230
- * @throws {@link CommsRequestError} If the registration fails
231
- * @see {@link https://datatracker.ietf.org/doc/html/rfc7591 RFC 7591}
232
- */
233
- async function registerClient(args, options) {
234
- const registerUrl = `${getAuthBaseUrl(options?.baseUrl)}/oauth/register`;
235
- const response = await (0, http_client_1.request)({
236
- httpMethod: 'POST',
237
- baseUri: registerUrl,
238
- relativePath: '',
239
- payload: { ...args, scope: args.scope?.join(' ') },
240
- customFetch: options?.customFetch,
241
- });
242
- if (!(0, http_client_1.isSuccess)(response) || !response.data?.clientId) {
243
- throw new errors_1.CommsRequestError('Dynamic client registration failed.', response.status, response.data);
244
- }
245
- const { clientIdIssuedAt, clientSecretExpiresAt, scope, ...rest } = response.data;
246
- return {
247
- ...rest,
248
- scope: scope ? scope.split(' ') : undefined,
249
- clientIdIssuedAt: clientIdIssuedAt !== undefined ? new Date(clientIdIssuedAt * 1000) : undefined,
250
- clientSecretExpiresAt: clientSecretExpiresAt === undefined
251
- ? undefined
252
- : clientSecretExpiresAt === 0
253
- ? null
254
- : new Date(clientSecretExpiresAt * 1000),
255
- };
256
- }
@@ -1,43 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addCommentRequest = addCommentRequest;
4
- const endpoints_1 = require("../consts/endpoints");
5
- const http_client_1 = require("../transport/http-client");
6
- const uuidv7_1 = require("../utils/uuidv7");
7
- const notify_audience_1 = require("./notify-audience");
8
- /**
9
- * Internal helper that powers `comments.createComment`,
10
- * `threads.closeThread`, and `threads.reopenThread`.
11
- *
12
- * Normalizes the `notifyAudience` flag into a sentinel `groups` entry,
13
- * rejects sentinel IDs passed via `groups` / `directGroupMentions`, mints a
14
- * UUIDv7 `id` when the caller omits one, and posts to `/comments/add`. When
15
- * `threadAction` is set (`'close'` / `'reopen'`), it is forwarded on the
16
- * wire so the same request both adds the comment and transitions the
17
- * parent thread.
18
- *
19
- * @param context - Per-call client context (base URI, API token, optional `customFetch`).
20
- * @param params - The comment payload (`{@link CreateCommentArgs}`).
21
- * @param options - Optional configuration.
22
- * @param options.threadAction - When set, also transitions the parent thread (`'close'` or `'reopen'`).
23
- * @returns The parsed {@link Comment} returned by the API.
24
- */
25
- function addCommentRequest(context, params, options) {
26
- const normalized = (0, notify_audience_1.applyNotifyAudience)(params);
27
- const withId = {
28
- ...normalized,
29
- id: (0, uuidv7_1.resolveCreateId)(normalized.id),
30
- threadId: (0, uuidv7_1.resolveReferenceId)(normalized.threadId, 'threadId'),
31
- };
32
- const payload = options?.threadAction
33
- ? { ...withId, threadAction: options.threadAction }
34
- : withId;
35
- return (0, http_client_1.request)({
36
- httpMethod: 'POST',
37
- baseUri: context.baseUri,
38
- relativePath: `${endpoints_1.ENDPOINT_COMMENTS}/add`,
39
- apiToken: context.apiToken,
40
- payload,
41
- customFetch: context.customFetch,
42
- }).then((response) => context.schema.parse(response.data));
43
- }
@@ -1,71 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AttachmentsClient = void 0;
4
- const endpoints_1 = require("../consts/endpoints");
5
- const entities_1 = require("../types/entities");
6
- const multipart_upload_1 = require("../utils/multipart-upload");
7
- const uuidv7_1 = require("../utils/uuidv7");
8
- const base_client_1 = require("./base-client");
9
- /**
10
- * Resolve the `attachment_id`: validate a caller-supplied value (throwing an
11
- * {@link UuidV7Error} that names `attachmentId` rather than the generic `id`) or
12
- * mint a fresh one. Wraps {@link resolveCreateId} so the error contract matches the
13
- * client's actual parameter.
14
- */
15
- function resolveAttachmentId(attachmentId) {
16
- if (attachmentId !== undefined && !(0, uuidv7_1.isValidUuidV7Base58)(attachmentId)) {
17
- throw new uuidv7_1.UuidV7Error(`invalid attachmentId ${JSON.stringify(attachmentId)} — use generateId() or omit \`attachmentId\` and let the SDK mint one.`);
18
- }
19
- return (0, uuidv7_1.resolveCreateId)(attachmentId);
20
- }
21
- /**
22
- * Client for uploading file attachments to Comms.
23
- *
24
- * Attachments are uploaded independently, then referenced by passing the returned
25
- * {@link Attachment} into the `attachments` array of `comments.createComment`,
26
- * `conversationMessages.createMessage`, and similar calls.
27
- */
28
- class AttachmentsClient extends base_client_1.BaseClient {
29
- /**
30
- * Uploads a file and returns the created {@link Attachment}.
31
- *
32
- * Mirrors the canonical multipart upload: `POST /attachments/upload` with the `file`
33
- * binary plus `file_name`, `file_size`, `attachment_id`, and `underlying_type` form
34
- * fields.
35
- *
36
- * @param args - The file to upload and optional metadata.
37
- * @returns The created attachment, ready to attach to a comment or message.
38
- *
39
- * @example
40
- * ```typescript
41
- * import { readFile } from 'node:fs/promises'
42
- *
43
- * const attachment = await api.attachments.upload({
44
- * file: await readFile('./diagram.png'),
45
- * fileName: 'diagram.png',
46
- * })
47
- *
48
- * await api.comments.createComment({
49
- * threadId: '7YpL3oZ4kZ9vP7Q1tR2sX3z',
50
- * content: 'See attached',
51
- * attachments: [attachment],
52
- * })
53
- * ```
54
- */
55
- async upload(args) {
56
- const data = await (0, multipart_upload_1.uploadMultipartFile)({
57
- baseUrl: this.getBaseUri(),
58
- authToken: this.apiToken,
59
- endpoint: `${endpoints_1.ENDPOINT_ATTACHMENTS}/upload`,
60
- file: args.file,
61
- fileName: args.fileName,
62
- contentType: args.contentType,
63
- additionalFields: {
64
- attachment_id: resolveAttachmentId(args.attachmentId),
65
- },
66
- customFetch: this.customFetch,
67
- });
68
- return entities_1.AttachmentSchema.parse(data);
69
- }
70
- }
71
- exports.AttachmentsClient = AttachmentsClient;
@@ -1,33 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BaseClient = void 0;
4
- const endpoints_1 = require("../consts/endpoints");
5
- const api_version_1 = require("../types/api-version");
6
- /**
7
- * Base class for every Comms API client. Centralizes URL handling and
8
- * config so individual clients stay focused on their endpoints.
9
- */
10
- class BaseClient {
11
- constructor(config) {
12
- this.apiToken = config.apiToken;
13
- this.baseUrl = config.baseUrl;
14
- this.defaultVersion = config.version || api_version_1.DEFAULT_API_VERSION;
15
- this.customFetch = config.customFetch;
16
- }
17
- /**
18
- * Returns the base URI for an API request, with a guaranteed trailing
19
- * slash so relative paths resolve cleanly through `URL`.
20
- */
21
- getBaseUri() {
22
- return (0, endpoints_1.getCommsBaseUri)(this.defaultVersion, this.baseUrl);
23
- }
24
- /**
25
- * Base URL for entity web links, or `undefined` to use getFullCommsURL's
26
- * default web app. Trailing-slash normalization happens in
27
- * `getFullCommsURL`, so the configured value is returned verbatim.
28
- */
29
- getLinkBaseUrl() {
30
- return this.baseUrl;
31
- }
32
- }
33
- exports.BaseClient = BaseClient;