@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 +19 -1
- package/dist/cjs/authentication.js +55 -10
- package/dist/cjs/testUtils/test-defaults.js +4 -4
- package/dist/cjs/utils/url-helpers.js +14 -14
- package/dist/esm/authentication.js +54 -10
- package/dist/esm/testUtils/test-defaults.js +4 -4
- package/dist/esm/utils/url-helpers.js +14 -14
- package/dist/types/authentication.d.ts +23 -0
- package/dist/types/utils/url-helpers.d.ts +2 -2
- package/package.json +1 -1
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 =
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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 '/
|
|
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 =
|
|
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
|
|
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
|
|
98
|
+
return `/${workspaceId}/inbox${tabParam}`;
|
|
99
99
|
}
|
|
100
100
|
/** Returns the URL for the messages/conversations root. */
|
|
101
101
|
function getMessagesRootURL(workspaceId) {
|
|
102
|
-
return
|
|
102
|
+
return `/${workspaceId}/msg`;
|
|
103
103
|
}
|
|
104
104
|
/** Returns the URL for a user profile. */
|
|
105
105
|
function getUserProfileURL(params) {
|
|
106
|
-
return
|
|
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
|
|
110
|
+
return `/${workspaceId}/saved`;
|
|
111
111
|
}
|
|
112
112
|
/** Returns the URL for a saved thread. */
|
|
113
113
|
function getSavedThreadURL(params) {
|
|
114
|
-
return
|
|
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
|
|
118
|
+
return `/${workspaceId}/search`;
|
|
119
119
|
}
|
|
120
120
|
/** Returns the URL for a search with a query. */
|
|
121
121
|
function getSearchQueryURL(params) {
|
|
122
|
-
return
|
|
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
|
-
?
|
|
128
|
-
:
|
|
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
|
|
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 =
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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 '/
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
83
|
+
return `/${workspaceId}/msg`;
|
|
84
84
|
}
|
|
85
85
|
/** Returns the URL for a user profile. */
|
|
86
86
|
export function getUserProfileURL(params) {
|
|
87
|
-
return
|
|
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
|
|
91
|
+
return `/${workspaceId}/saved`;
|
|
92
92
|
}
|
|
93
93
|
/** Returns the URL for a saved thread. */
|
|
94
94
|
export function getSavedThreadURL(params) {
|
|
95
|
-
return
|
|
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
|
|
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
|
|
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
|
-
?
|
|
109
|
-
:
|
|
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
|
|
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
|
|
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 '/
|
|
19
|
+
* // returns '/1/ch/7Yp.../t/7Yq.../'
|
|
20
20
|
*/
|
|
21
21
|
export declare function getCommsURL(params: CommsURLParams): string;
|
|
22
22
|
/**
|