@doist/comms-sdk 0.4.5 → 0.5.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.
package/README.md CHANGED
@@ -103,6 +103,24 @@ const api = new CommsApi(tokenResponse.accessToken)
103
103
  const user = await api.users.getSessionUser()
104
104
  ```
105
105
 
106
+ Access tokens expire (see `tokenResponse.expiresIn`). When they do, use the
107
+ `refreshToken` from the original exchange to obtain a new one without sending
108
+ the user back through the authorization flow:
109
+
110
+ ```typescript
111
+ import { refreshAuthToken } from '@doist/comms-sdk'
112
+
113
+ const refreshed = await refreshAuthToken({
114
+ clientId: 'your-client-id',
115
+ clientSecret: 'your-client-secret',
116
+ refreshToken: tokenResponse.refreshToken,
117
+ })
118
+
119
+ // The server may rotate the refresh token — persist refreshed.refreshToken
120
+ // when present, otherwise keep using the previous one.
121
+ const api = new CommsApi(refreshed.accessToken)
122
+ ```
123
+
106
124
  ### Short-lived processes (CLIs, scripts)
107
125
 
108
126
  On Node, the SDK keeps a connection pool alive across requests so HTTP/2
@@ -122,7 +140,7 @@ try {
122
140
 
123
141
  `api.close()` drains the process-global pool, so it also covers code
124
142
  paths that only use the standalone OAuth helpers (`getAuthToken`,
125
- `revokeAuthToken`, `registerClient`). Those flows can also import
143
+ `refreshAuthToken`, `revokeAuthToken`, `registerClient`). Those flows can also import
126
144
  `closeDefaultDispatcher` directly.
127
145
 
128
146
  ## Development
@@ -4,11 +4,25 @@ exports.TOKEN_ENDPOINT_AUTH_METHODS = exports.COMMS_SCOPES = void 0;
4
4
  exports.getAuthStateParameter = getAuthStateParameter;
5
5
  exports.getAuthorizationUrl = getAuthorizationUrl;
6
6
  exports.getAuthToken = getAuthToken;
7
+ exports.refreshAuthToken = refreshAuthToken;
7
8
  exports.revokeAuthToken = revokeAuthToken;
8
9
  exports.registerClient = registerClient;
9
10
  const uuid_1 = require("uuid");
10
11
  const http_client_1 = require("./transport/http-client");
11
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
+ }
12
26
  /**
13
27
  * OAuth scopes for the Comms API.
14
28
  *
@@ -113,7 +127,7 @@ function getAuthorizationUrl(clientId, scopes, state, redirectUri, baseUrl) {
113
127
  if (!scopes?.length) {
114
128
  throw new Error('At least one scope value is required.');
115
129
  }
116
- const authBaseUrl = baseUrl ? `${baseUrl}/oauth` : 'https://comms.todoist.com/oauth';
130
+ const authBaseUrl = `${getAuthBaseUrl(baseUrl)}/oauth`;
117
131
  const scope = scopes.join(' ');
118
132
  const params = new URLSearchParams({
119
133
  client_id: clientId,
@@ -127,9 +141,7 @@ function getAuthorizationUrl(clientId, scopes, state, redirectUri, baseUrl) {
127
141
  return `${authBaseUrl}/authorize?${params.toString()}`;
128
142
  }
129
143
  async function getAuthToken(args, options) {
130
- const tokenUrl = options?.baseUrl
131
- ? `${options.baseUrl}/oauth/token`
132
- : 'https://comms.todoist.com/oauth/token';
144
+ const tokenUrl = `${getAuthBaseUrl(options?.baseUrl)}/oauth/access_token`;
133
145
  const payload = {
134
146
  clientId: args.clientId,
135
147
  clientSecret: args.clientSecret,
@@ -149,10 +161,45 @@ async function getAuthToken(args, options) {
149
161
  }
150
162
  return response.data;
151
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
+ }
152
201
  async function revokeAuthToken(args, options) {
153
- const revokeUrl = options?.baseUrl
154
- ? `${options.baseUrl}/oauth/revoke`
155
- : 'https://comms.todoist.com/oauth/revoke';
202
+ const revokeUrl = `${getAuthBaseUrl(options?.baseUrl)}/api/v1/revoke`;
156
203
  const response = await (0, http_client_1.request)({
157
204
  httpMethod: 'POST',
158
205
  baseUri: revokeUrl,
@@ -184,9 +231,7 @@ async function revokeAuthToken(args, options) {
184
231
  * @see {@link https://datatracker.ietf.org/doc/html/rfc7591 RFC 7591}
185
232
  */
186
233
  async function registerClient(args, options) {
187
- const registerUrl = options?.baseUrl
188
- ? `${options.baseUrl}/oauth/register`
189
- : 'https://comms.todoist.com/oauth/register';
234
+ const registerUrl = `${getAuthBaseUrl(options?.baseUrl)}/oauth/register`;
190
235
  const response = await (0, http_client_1.request)({
191
236
  httpMethod: 'POST',
192
237
  baseUri: registerUrl,
@@ -35,7 +35,7 @@ exports.mockChannel = {
35
35
  archived: false,
36
36
  created: new Date('2021-01-01T00:00:00Z'),
37
37
  version: 0,
38
- url: `https://comms.todoist.com/a/1/ch/${exports.TEST_CHANNEL_ID}/`,
38
+ url: `https://comms.todoist.com/1/ch/${exports.TEST_CHANNEL_ID}/`,
39
39
  };
40
40
  exports.mockThread = {
41
41
  id: exports.TEST_THREAD_ID,
@@ -51,7 +51,7 @@ exports.mockThread = {
51
51
  snippet: 'This is a test thread',
52
52
  snippetCreator: 1,
53
53
  isArchived: false,
54
- url: `https://comms.todoist.com/a/1/ch/${exports.TEST_CHANNEL_ID}/t/${exports.TEST_THREAD_ID}/`,
54
+ url: `https://comms.todoist.com/1/ch/${exports.TEST_CHANNEL_ID}/t/${exports.TEST_THREAD_ID}/`,
55
55
  };
56
56
  exports.mockGroup = {
57
57
  id: exports.TEST_GROUP_ID,
@@ -72,7 +72,7 @@ exports.mockConversation = {
72
72
  archived: false,
73
73
  created: new Date('2021-01-01T00:00:00Z'),
74
74
  creator: 1,
75
- url: `https://comms.todoist.com/a/1/msg/${exports.TEST_CONVERSATION_ID}/`,
75
+ url: `https://comms.todoist.com/1/msg/${exports.TEST_CONVERSATION_ID}/`,
76
76
  };
77
77
  exports.mockComment = {
78
78
  id: exports.TEST_COMMENT_ID,
@@ -82,7 +82,7 @@ exports.mockComment = {
82
82
  workspaceId: 1,
83
83
  channelId: exports.TEST_CHANNEL_ID,
84
84
  posted: new Date('2021-01-01T00:00:00Z'),
85
- url: `https://comms.todoist.com/a/1/ch/${exports.TEST_CHANNEL_ID}/t/${exports.TEST_THREAD_ID}/c/${exports.TEST_COMMENT_ID}`,
85
+ url: `https://comms.todoist.com/1/ch/${exports.TEST_CHANNEL_ID}/t/${exports.TEST_THREAD_ID}/c/${exports.TEST_COMMENT_ID}`,
86
86
  };
87
87
  exports.mockWorkspaceUser = {
88
88
  id: 1,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Helper functions for creating Comms permalinks (`https://comms.todoist.com/a/...`).
3
+ * Helper functions for creating Comms permalinks (`https://comms.todoist.com/...`).
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getCommsURL = getCommsURL;
@@ -27,11 +27,11 @@ const COMMS_BASE_URL = 'https://comms.todoist.com';
27
27
  * @returns A relative URL path
28
28
  * @example
29
29
  * getCommsURL({ workspaceId: 1, channelId: '7Yp...', threadId: '7Yq...' })
30
- * // returns '/a/1/ch/7Yp.../t/7Yq.../'
30
+ * // returns '/1/ch/7Yp.../t/7Yq.../'
31
31
  */
32
32
  function getCommsURL(params) {
33
33
  const { workspaceId, channelId, conversationId, threadId, commentId, messageId, userId } = params;
34
- let url = `/a/${workspaceId}/`;
34
+ let url = `/${workspaceId}/`;
35
35
  if (channelId) {
36
36
  url += `ch/${channelId}/`;
37
37
  if (threadId) {
@@ -90,44 +90,44 @@ function getCommentURL(params) {
90
90
  }
91
91
  /** Returns the URL for the threads root (channels view). */
92
92
  function getThreadsRootURL(workspaceId) {
93
- return `/a/${workspaceId}/ch`;
93
+ return `/${workspaceId}/ch`;
94
94
  }
95
95
  /** Returns the URL for the inbox. */
96
96
  function getInboxURL(workspaceId, tab) {
97
97
  const tabParam = tab ? `/${tab}` : '';
98
- return `/a/${workspaceId}/inbox${tabParam}`;
98
+ return `/${workspaceId}/inbox${tabParam}`;
99
99
  }
100
100
  /** Returns the URL for the messages/conversations root. */
101
101
  function getMessagesRootURL(workspaceId) {
102
- return `/a/${workspaceId}/msg`;
102
+ return `/${workspaceId}/msg`;
103
103
  }
104
104
  /** Returns the URL for a user profile. */
105
105
  function getUserProfileURL(params) {
106
- return `/a/${params.workspaceId}/people/u/${params.userId}`;
106
+ return `/${params.workspaceId}/people/u/${params.userId}`;
107
107
  }
108
108
  /** Returns the URL for the saved threads view. */
109
109
  function getSavedThreadsRootURL(workspaceId) {
110
- return `/a/${workspaceId}/saved`;
110
+ return `/${workspaceId}/saved`;
111
111
  }
112
112
  /** Returns the URL for a saved thread. */
113
113
  function getSavedThreadURL(params) {
114
- return `/a/${params.workspaceId}/saved/t/${params.threadId}`;
114
+ return `/${params.workspaceId}/saved/t/${params.threadId}`;
115
115
  }
116
116
  /** Returns the URL for the search root. */
117
117
  function getSearchRootURL(workspaceId) {
118
- return `/a/${workspaceId}/search`;
118
+ return `/${workspaceId}/search`;
119
119
  }
120
120
  /** Returns the URL for a search with a query. */
121
121
  function getSearchQueryURL(params) {
122
- return `/a/${params.workspaceId}/search?q=${decodeURIComponent(params.query)}`;
122
+ return `/${params.workspaceId}/search?q=${decodeURIComponent(params.query)}`;
123
123
  }
124
124
  /** Returns the URL for settings. */
125
125
  function getSettingsURL(params) {
126
126
  return params.initialLocation
127
- ? `/a/${params.workspaceId}/settings/${params.initialLocation}`
128
- : `/a/${params.workspaceId}/settings`;
127
+ ? `/${params.workspaceId}/settings/${params.initialLocation}`
128
+ : `/${params.workspaceId}/settings`;
129
129
  }
130
130
  /** Returns the URL for the team members root. */
131
131
  function getTeamMembersRootURL(workspaceId) {
132
- return `/a/${workspaceId}/people/u`;
132
+ return `/${workspaceId}/people/u`;
133
133
  }
@@ -1,6 +1,19 @@
1
1
  import { v4 as uuid } from 'uuid';
2
2
  import { isSuccess, request } from './transport/http-client.js';
3
3
  import { CommsRequestError } from './types/errors.js';
4
+ /**
5
+ * Default origin of the Comms authorization server.
6
+ *
7
+ * The authorization server for Comms is Todoist (`todoist.com`), not the
8
+ * resource server (`comms.todoist.com`). Tokens are issued by Todoist and
9
+ * accepted by the Comms API. Paths match Todoist's published
10
+ * `/.well-known/oauth-authorization-server` metadata.
11
+ */
12
+ const DEFAULT_AUTH_BASE_URL = 'https://todoist.com';
13
+ function getAuthBaseUrl(baseUrl) {
14
+ const base = baseUrl ?? DEFAULT_AUTH_BASE_URL;
15
+ return base.endsWith('/') ? base.slice(0, -1) : base;
16
+ }
4
17
  /**
5
18
  * OAuth scopes for the Comms API.
6
19
  *
@@ -105,7 +118,7 @@ export function getAuthorizationUrl(clientId, scopes, state, redirectUri, baseUr
105
118
  if (!scopes?.length) {
106
119
  throw new Error('At least one scope value is required.');
107
120
  }
108
- const authBaseUrl = baseUrl ? `${baseUrl}/oauth` : 'https://comms.todoist.com/oauth';
121
+ const authBaseUrl = `${getAuthBaseUrl(baseUrl)}/oauth`;
109
122
  const scope = scopes.join(' ');
110
123
  const params = new URLSearchParams({
111
124
  client_id: clientId,
@@ -119,9 +132,7 @@ export function getAuthorizationUrl(clientId, scopes, state, redirectUri, baseUr
119
132
  return `${authBaseUrl}/authorize?${params.toString()}`;
120
133
  }
121
134
  export async function getAuthToken(args, options) {
122
- const tokenUrl = options?.baseUrl
123
- ? `${options.baseUrl}/oauth/token`
124
- : 'https://comms.todoist.com/oauth/token';
135
+ const tokenUrl = `${getAuthBaseUrl(options?.baseUrl)}/oauth/access_token`;
125
136
  const payload = {
126
137
  clientId: args.clientId,
127
138
  clientSecret: args.clientSecret,
@@ -141,10 +152,45 @@ export async function getAuthToken(args, options) {
141
152
  }
142
153
  return response.data;
143
154
  }
155
+ /**
156
+ * Exchanges a refresh token for a new access token using the OAuth2
157
+ * `refresh_token` grant.
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const { accessToken, refreshToken } = await refreshAuthToken({
162
+ * clientId: 'client-id',
163
+ * clientSecret: 'client-secret',
164
+ * refreshToken: storedRefreshToken,
165
+ * })
166
+ * ```
167
+ *
168
+ * @returns The refreshed token. The response may include a new `refreshToken`
169
+ * (rotated by the server); persist it in place of the previous one when present.
170
+ * @throws {@link CommsRequestError} If the refresh fails
171
+ */
172
+ export async function refreshAuthToken(args, options) {
173
+ const tokenUrl = `${getAuthBaseUrl(options?.baseUrl)}/oauth/access_token`;
174
+ const payload = {
175
+ clientId: args.clientId,
176
+ clientSecret: args.clientSecret,
177
+ refreshToken: args.refreshToken,
178
+ grantType: 'refresh_token',
179
+ };
180
+ const response = await request({
181
+ httpMethod: 'POST',
182
+ baseUri: tokenUrl,
183
+ relativePath: '',
184
+ payload,
185
+ customFetch: options?.customFetch,
186
+ });
187
+ if (!isSuccess(response) || !response.data?.accessToken) {
188
+ throw new CommsRequestError('Authentication token refresh failed.', response.status, response.data);
189
+ }
190
+ return response.data;
191
+ }
144
192
  export async function revokeAuthToken(args, options) {
145
- const revokeUrl = options?.baseUrl
146
- ? `${options.baseUrl}/oauth/revoke`
147
- : 'https://comms.todoist.com/oauth/revoke';
193
+ const revokeUrl = `${getAuthBaseUrl(options?.baseUrl)}/api/v1/revoke`;
148
194
  const response = await request({
149
195
  httpMethod: 'POST',
150
196
  baseUri: revokeUrl,
@@ -176,9 +222,7 @@ export async function revokeAuthToken(args, options) {
176
222
  * @see {@link https://datatracker.ietf.org/doc/html/rfc7591 RFC 7591}
177
223
  */
178
224
  export async function registerClient(args, options) {
179
- const registerUrl = options?.baseUrl
180
- ? `${options.baseUrl}/oauth/register`
181
- : 'https://comms.todoist.com/oauth/register';
225
+ const registerUrl = `${getAuthBaseUrl(options?.baseUrl)}/oauth/register`;
182
226
  const response = await request({
183
227
  httpMethod: 'POST',
184
228
  baseUri: registerUrl,
@@ -32,7 +32,7 @@ export const mockChannel = {
32
32
  archived: false,
33
33
  created: new Date('2021-01-01T00:00:00Z'),
34
34
  version: 0,
35
- url: `https://comms.todoist.com/a/1/ch/${TEST_CHANNEL_ID}/`,
35
+ url: `https://comms.todoist.com/1/ch/${TEST_CHANNEL_ID}/`,
36
36
  };
37
37
  export const mockThread = {
38
38
  id: TEST_THREAD_ID,
@@ -48,7 +48,7 @@ export const mockThread = {
48
48
  snippet: 'This is a test thread',
49
49
  snippetCreator: 1,
50
50
  isArchived: false,
51
- url: `https://comms.todoist.com/a/1/ch/${TEST_CHANNEL_ID}/t/${TEST_THREAD_ID}/`,
51
+ url: `https://comms.todoist.com/1/ch/${TEST_CHANNEL_ID}/t/${TEST_THREAD_ID}/`,
52
52
  };
53
53
  export const mockGroup = {
54
54
  id: TEST_GROUP_ID,
@@ -69,7 +69,7 @@ export const mockConversation = {
69
69
  archived: false,
70
70
  created: new Date('2021-01-01T00:00:00Z'),
71
71
  creator: 1,
72
- url: `https://comms.todoist.com/a/1/msg/${TEST_CONVERSATION_ID}/`,
72
+ url: `https://comms.todoist.com/1/msg/${TEST_CONVERSATION_ID}/`,
73
73
  };
74
74
  export const mockComment = {
75
75
  id: TEST_COMMENT_ID,
@@ -79,7 +79,7 @@ export const mockComment = {
79
79
  workspaceId: 1,
80
80
  channelId: TEST_CHANNEL_ID,
81
81
  posted: new Date('2021-01-01T00:00:00Z'),
82
- url: `https://comms.todoist.com/a/1/ch/${TEST_CHANNEL_ID}/t/${TEST_THREAD_ID}/c/${TEST_COMMENT_ID}`,
82
+ url: `https://comms.todoist.com/1/ch/${TEST_CHANNEL_ID}/t/${TEST_THREAD_ID}/c/${TEST_COMMENT_ID}`,
83
83
  };
84
84
  export const mockWorkspaceUser = {
85
85
  id: 1,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Helper functions for creating Comms permalinks (`https://comms.todoist.com/a/...`).
2
+ * Helper functions for creating Comms permalinks (`https://comms.todoist.com/...`).
3
3
  */
4
4
  const COMMS_BASE_URL = 'https://comms.todoist.com';
5
5
  /**
@@ -8,11 +8,11 @@ const COMMS_BASE_URL = 'https://comms.todoist.com';
8
8
  * @returns A relative URL path
9
9
  * @example
10
10
  * getCommsURL({ workspaceId: 1, channelId: '7Yp...', threadId: '7Yq...' })
11
- * // returns '/a/1/ch/7Yp.../t/7Yq.../'
11
+ * // returns '/1/ch/7Yp.../t/7Yq.../'
12
12
  */
13
13
  export function getCommsURL(params) {
14
14
  const { workspaceId, channelId, conversationId, threadId, commentId, messageId, userId } = params;
15
- let url = `/a/${workspaceId}/`;
15
+ let url = `/${workspaceId}/`;
16
16
  if (channelId) {
17
17
  url += `ch/${channelId}/`;
18
18
  if (threadId) {
@@ -71,44 +71,44 @@ export function getCommentURL(params) {
71
71
  }
72
72
  /** Returns the URL for the threads root (channels view). */
73
73
  export function getThreadsRootURL(workspaceId) {
74
- return `/a/${workspaceId}/ch`;
74
+ return `/${workspaceId}/ch`;
75
75
  }
76
76
  /** Returns the URL for the inbox. */
77
77
  export function getInboxURL(workspaceId, tab) {
78
78
  const tabParam = tab ? `/${tab}` : '';
79
- return `/a/${workspaceId}/inbox${tabParam}`;
79
+ return `/${workspaceId}/inbox${tabParam}`;
80
80
  }
81
81
  /** Returns the URL for the messages/conversations root. */
82
82
  export function getMessagesRootURL(workspaceId) {
83
- return `/a/${workspaceId}/msg`;
83
+ return `/${workspaceId}/msg`;
84
84
  }
85
85
  /** Returns the URL for a user profile. */
86
86
  export function getUserProfileURL(params) {
87
- return `/a/${params.workspaceId}/people/u/${params.userId}`;
87
+ return `/${params.workspaceId}/people/u/${params.userId}`;
88
88
  }
89
89
  /** Returns the URL for the saved threads view. */
90
90
  export function getSavedThreadsRootURL(workspaceId) {
91
- return `/a/${workspaceId}/saved`;
91
+ return `/${workspaceId}/saved`;
92
92
  }
93
93
  /** Returns the URL for a saved thread. */
94
94
  export function getSavedThreadURL(params) {
95
- return `/a/${params.workspaceId}/saved/t/${params.threadId}`;
95
+ return `/${params.workspaceId}/saved/t/${params.threadId}`;
96
96
  }
97
97
  /** Returns the URL for the search root. */
98
98
  export function getSearchRootURL(workspaceId) {
99
- return `/a/${workspaceId}/search`;
99
+ return `/${workspaceId}/search`;
100
100
  }
101
101
  /** Returns the URL for a search with a query. */
102
102
  export function getSearchQueryURL(params) {
103
- return `/a/${params.workspaceId}/search?q=${decodeURIComponent(params.query)}`;
103
+ return `/${params.workspaceId}/search?q=${decodeURIComponent(params.query)}`;
104
104
  }
105
105
  /** Returns the URL for settings. */
106
106
  export function getSettingsURL(params) {
107
107
  return params.initialLocation
108
- ? `/a/${params.workspaceId}/settings/${params.initialLocation}`
109
- : `/a/${params.workspaceId}/settings`;
108
+ ? `/${params.workspaceId}/settings/${params.initialLocation}`
109
+ : `/${params.workspaceId}/settings`;
110
110
  }
111
111
  /** Returns the URL for the team members root. */
112
112
  export function getTeamMembersRootURL(workspaceId) {
113
- return `/a/${workspaceId}/people/u`;
113
+ return `/${workspaceId}/people/u`;
114
114
  }
@@ -78,6 +78,11 @@ export type AuthTokenResponse = {
78
78
  expiresIn?: number;
79
79
  scope?: string;
80
80
  };
81
+ export type RefreshAuthTokenRequestArgs = {
82
+ clientId: string;
83
+ clientSecret: string;
84
+ refreshToken: string;
85
+ };
81
86
  export type RevokeAuthTokenRequestArgs = {
82
87
  clientId: string;
83
88
  clientSecret: string;
@@ -138,6 +143,24 @@ export declare function getAuthStateParameter(): string;
138
143
  */
139
144
  export declare function getAuthorizationUrl(clientId: string, scopes: CommsScope[], state: string, redirectUri?: string, baseUrl?: string): string;
140
145
  export declare function getAuthToken(args: AuthTokenRequestArgs, options?: AuthOptions): Promise<AuthTokenResponse>;
146
+ /**
147
+ * Exchanges a refresh token for a new access token using the OAuth2
148
+ * `refresh_token` grant.
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * const { accessToken, refreshToken } = await refreshAuthToken({
153
+ * clientId: 'client-id',
154
+ * clientSecret: 'client-secret',
155
+ * refreshToken: storedRefreshToken,
156
+ * })
157
+ * ```
158
+ *
159
+ * @returns The refreshed token. The response may include a new `refreshToken`
160
+ * (rotated by the server); persist it in place of the previous one when present.
161
+ * @throws {@link CommsRequestError} If the refresh fails
162
+ */
163
+ export declare function refreshAuthToken(args: RefreshAuthTokenRequestArgs, options?: AuthOptions): Promise<AuthTokenResponse>;
141
164
  export declare function revokeAuthToken(args: RevokeAuthTokenRequestArgs, options?: AuthOptions): Promise<boolean>;
142
165
  /**
143
166
  * Registers a new OAuth client via Dynamic Client Registration (RFC 7591).
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Helper functions for creating Comms permalinks (`https://comms.todoist.com/a/...`).
2
+ * Helper functions for creating Comms permalinks (`https://comms.todoist.com/...`).
3
3
  */
4
4
  export type CommsURLParams = {
5
5
  workspaceId: number;
@@ -16,7 +16,7 @@ export type CommsURLParams = {
16
16
  * @returns A relative URL path
17
17
  * @example
18
18
  * getCommsURL({ workspaceId: 1, channelId: '7Yp...', threadId: '7Yq...' })
19
- * // returns '/a/1/ch/7Yp.../t/7Yq.../'
19
+ * // returns '/1/ch/7Yp.../t/7Yq.../'
20
20
  */
21
21
  export declare function getCommsURL(params: CommsURLParams): string;
22
22
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/comms-sdk",
3
- "version": "0.4.5",
3
+ "version": "0.5.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/",