@doist/todoist-sdk 10.4.1 → 10.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
@@ -104,6 +104,27 @@ await revokeToken(args, {
104
104
  const { accessToken } = await getAuthToken(args, baseUrl)
105
105
  ```
106
106
 
107
+ When the authorization server issues a `refreshToken`, use `refreshAuthToken`
108
+ to obtain a new access token once the current one expires, without sending the
109
+ user back through the authorization flow:
110
+
111
+ ```typescript
112
+ const refreshed = await refreshAuthToken({
113
+ clientId: 'your-client-id',
114
+ clientSecret: 'your-client-secret',
115
+ refreshToken: storedRefreshToken,
116
+ })
117
+
118
+ // The server may rotate the refresh token — persist refreshed.refreshToken
119
+ // when present, otherwise keep using the previous one.
120
+ ```
121
+
122
+ > **Note:** Refresh tokens are only issued to OAuth apps that have them enabled
123
+ > (the default for new apps). Apps without them receive a long-lived access
124
+ > token, so `getAuthToken` returns no `refreshToken` and there is nothing to
125
+ > refresh. For public clients (`tokenEndpointAuthMethod: 'none'`), omit
126
+ > `clientSecret` when calling `refreshAuthToken`.
127
+
107
128
  ### Important Notes
108
129
 
109
130
  - All existing transforms (snake_case ↔ camelCase) work automatically with custom fetch
@@ -15,6 +15,7 @@ exports.TOKEN_ENDPOINT_AUTH_METHODS = exports.PERMISSIONS = void 0;
15
15
  exports.getAuthStateParameter = getAuthStateParameter;
16
16
  exports.getAuthorizationUrl = getAuthorizationUrl;
17
17
  exports.getAuthToken = getAuthToken;
18
+ exports.refreshAuthToken = refreshAuthToken;
18
19
  exports.revokeToken = revokeToken;
19
20
  exports.migratePersonalToken = migratePersonalToken;
20
21
  exports.registerClient = registerClient;
@@ -108,32 +109,74 @@ function getAuthorizationUrl({ clientId, permissions, state, baseUrl, }) {
108
109
  * @returns The access token response
109
110
  * @throws {@link TodoistRequestError} If the token exchange fails
110
111
  */
111
- async function getAuthToken(args, options) {
112
+ /**
113
+ * Posts a payload to the OAuth token endpoint and returns the token response,
114
+ * mapping any failure to a {@link TodoistRequestError} with `errorMessage`.
115
+ * Shared by {@link getAuthToken} and {@link refreshAuthToken}.
116
+ */
117
+ async function requestAuthToken(payload, errorMessage, options) {
112
118
  var _a;
113
- if (typeof options === 'string') {
114
- throw new TypeError('Passing baseUrl as a string is no longer supported. Use an options object instead: getAuthToken(args, { baseUrl })');
115
- }
116
- const baseUrl = options === null || options === void 0 ? void 0 : options.baseUrl;
117
- const customFetch = options === null || options === void 0 ? void 0 : options.customFetch;
118
119
  try {
119
120
  const response = await (0, http_client_1.request)({
120
121
  httpMethod: 'POST',
121
- baseUri: (0, endpoints_1.getAuthBaseUri)(baseUrl),
122
+ baseUri: (0, endpoints_1.getAuthBaseUri)(options === null || options === void 0 ? void 0 : options.baseUrl),
122
123
  relativePath: endpoints_1.ENDPOINT_GET_TOKEN,
123
124
  apiToken: undefined,
124
- payload: args,
125
- customFetch,
125
+ payload,
126
+ customFetch: options === null || options === void 0 ? void 0 : options.customFetch,
126
127
  });
127
128
  if (response.status !== 200 || !((_a = response.data) === null || _a === void 0 ? void 0 : _a.accessToken)) {
128
- throw new types_1.TodoistRequestError('Authentication token exchange failed.', response.status, response.data);
129
+ throw new types_1.TodoistRequestError(errorMessage, response.status, response.data);
129
130
  }
130
131
  return response.data;
131
132
  }
132
133
  catch (error) {
133
134
  // Re-throw with custom message for authentication failures
134
135
  const err = error;
135
- throw new types_1.TodoistRequestError('Authentication token exchange failed.', err.httpStatusCode, err.responseData);
136
+ throw new types_1.TodoistRequestError(errorMessage, err.httpStatusCode, err.responseData);
137
+ }
138
+ }
139
+ async function getAuthToken(args, options) {
140
+ if (typeof options === 'string') {
141
+ throw new TypeError('Passing baseUrl as a string is no longer supported. Use an options object instead: getAuthToken(args, { baseUrl })');
142
+ }
143
+ return requestAuthToken(Object.assign({}, args), 'Authentication token exchange failed.', options);
144
+ }
145
+ /**
146
+ * Exchanges a refresh token for a new access token using the OAuth2
147
+ * `refresh_token` grant.
148
+ *
149
+ * Only relevant for OAuth apps that have refresh tokens enabled (the default for
150
+ * new apps). Apps without them never receive a `refreshToken` from
151
+ * {@link getAuthToken}, so they have nothing to pass here — their access tokens
152
+ * are long-lived instead.
153
+ *
154
+ * Omit `clientSecret` for public clients registered with
155
+ * `tokenEndpointAuthMethod: 'none'`.
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * const { accessToken, refreshToken } = await refreshAuthToken({
160
+ * clientId: 'your-client-id',
161
+ * clientSecret: 'your-client-secret',
162
+ * refreshToken: storedRefreshToken,
163
+ * })
164
+ * ```
165
+ *
166
+ * @returns The refreshed token. The server rotates the refresh token, so the
167
+ * response carries a new `refreshToken` — persist it in place of the old one.
168
+ * @throws {@link TodoistRequestError} If the refresh fails
169
+ */
170
+ async function refreshAuthToken(args, options) {
171
+ const payload = {
172
+ clientId: args.clientId,
173
+ refreshToken: args.refreshToken,
174
+ grantType: 'refresh_token',
175
+ };
176
+ if (args.clientSecret !== undefined) {
177
+ payload.clientSecret = args.clientSecret;
136
178
  }
179
+ return requestAuthToken(payload, 'Authentication token refresh failed.', options);
137
180
  }
138
181
  /**
139
182
  * Revokes a token using the RFC 7009 OAuth 2.0 Token Revocation standard.
@@ -99,32 +99,74 @@ export function getAuthorizationUrl({ clientId, permissions, state, baseUrl, })
99
99
  * @returns The access token response
100
100
  * @throws {@link TodoistRequestError} If the token exchange fails
101
101
  */
102
- export async function getAuthToken(args, options) {
102
+ /**
103
+ * Posts a payload to the OAuth token endpoint and returns the token response,
104
+ * mapping any failure to a {@link TodoistRequestError} with `errorMessage`.
105
+ * Shared by {@link getAuthToken} and {@link refreshAuthToken}.
106
+ */
107
+ async function requestAuthToken(payload, errorMessage, options) {
103
108
  var _a;
104
- if (typeof options === 'string') {
105
- throw new TypeError('Passing baseUrl as a string is no longer supported. Use an options object instead: getAuthToken(args, { baseUrl })');
106
- }
107
- const baseUrl = options === null || options === void 0 ? void 0 : options.baseUrl;
108
- const customFetch = options === null || options === void 0 ? void 0 : options.customFetch;
109
109
  try {
110
110
  const response = await request({
111
111
  httpMethod: 'POST',
112
- baseUri: getAuthBaseUri(baseUrl),
112
+ baseUri: getAuthBaseUri(options === null || options === void 0 ? void 0 : options.baseUrl),
113
113
  relativePath: ENDPOINT_GET_TOKEN,
114
114
  apiToken: undefined,
115
- payload: args,
116
- customFetch,
115
+ payload,
116
+ customFetch: options === null || options === void 0 ? void 0 : options.customFetch,
117
117
  });
118
118
  if (response.status !== 200 || !((_a = response.data) === null || _a === void 0 ? void 0 : _a.accessToken)) {
119
- throw new TodoistRequestError('Authentication token exchange failed.', response.status, response.data);
119
+ throw new TodoistRequestError(errorMessage, response.status, response.data);
120
120
  }
121
121
  return response.data;
122
122
  }
123
123
  catch (error) {
124
124
  // Re-throw with custom message for authentication failures
125
125
  const err = error;
126
- throw new TodoistRequestError('Authentication token exchange failed.', err.httpStatusCode, err.responseData);
126
+ throw new TodoistRequestError(errorMessage, err.httpStatusCode, err.responseData);
127
+ }
128
+ }
129
+ export async function getAuthToken(args, options) {
130
+ if (typeof options === 'string') {
131
+ throw new TypeError('Passing baseUrl as a string is no longer supported. Use an options object instead: getAuthToken(args, { baseUrl })');
132
+ }
133
+ return requestAuthToken(Object.assign({}, args), 'Authentication token exchange failed.', options);
134
+ }
135
+ /**
136
+ * Exchanges a refresh token for a new access token using the OAuth2
137
+ * `refresh_token` grant.
138
+ *
139
+ * Only relevant for OAuth apps that have refresh tokens enabled (the default for
140
+ * new apps). Apps without them never receive a `refreshToken` from
141
+ * {@link getAuthToken}, so they have nothing to pass here — their access tokens
142
+ * are long-lived instead.
143
+ *
144
+ * Omit `clientSecret` for public clients registered with
145
+ * `tokenEndpointAuthMethod: 'none'`.
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const { accessToken, refreshToken } = await refreshAuthToken({
150
+ * clientId: 'your-client-id',
151
+ * clientSecret: 'your-client-secret',
152
+ * refreshToken: storedRefreshToken,
153
+ * })
154
+ * ```
155
+ *
156
+ * @returns The refreshed token. The server rotates the refresh token, so the
157
+ * response carries a new `refreshToken` — persist it in place of the old one.
158
+ * @throws {@link TodoistRequestError} If the refresh fails
159
+ */
160
+ export async function refreshAuthToken(args, options) {
161
+ const payload = {
162
+ clientId: args.clientId,
163
+ refreshToken: args.refreshToken,
164
+ grantType: 'refresh_token',
165
+ };
166
+ if (args.clientSecret !== undefined) {
167
+ payload.clientSecret = args.clientSecret;
127
168
  }
169
+ return requestAuthToken(payload, 'Authentication token refresh failed.', options);
128
170
  }
129
171
  /**
130
172
  * Revokes a token using the RFC 7009 OAuth 2.0 Token Revocation standard.
@@ -74,6 +74,31 @@ export type AuthTokenRequestArgs = {
74
74
  export type AuthTokenResponse = {
75
75
  accessToken: string;
76
76
  tokenType: string;
77
+ /**
78
+ * Refresh token for use with {@link refreshAuthToken}.
79
+ *
80
+ * Only present when the OAuth app has refresh tokens enabled — the default
81
+ * for new apps, and immutable once on. Apps without it receive a long-lived
82
+ * access token and no refresh token.
83
+ */
84
+ refreshToken?: string;
85
+ /** Access token lifetime in seconds. */
86
+ expiresIn?: number;
87
+ /** Space-separated granted scopes, when returned. */
88
+ scope?: string;
89
+ };
90
+ /**
91
+ * Parameters required to exchange a refresh token for a new access token.
92
+ * @see https://developer.todoist.com/api/v1/#tag/Authorization/OAuth
93
+ */
94
+ export type RefreshTokenRequestArgs = {
95
+ clientId: string;
96
+ /**
97
+ * Client secret. Required for confidential clients; omit for public clients
98
+ * registered with `tokenEndpointAuthMethod: 'none'`.
99
+ */
100
+ clientSecret?: string;
101
+ refreshToken: string;
77
102
  };
78
103
  /**
79
104
  * Parameters required to revoke a token using RFC 7009 compliant endpoint.
@@ -148,22 +173,33 @@ export declare function getAuthorizationUrl({ clientId, permissions, state, base
148
173
  state: string;
149
174
  baseUrl?: string;
150
175
  }): string;
176
+ export declare function getAuthToken(args: AuthTokenRequestArgs, options?: AuthOptions): Promise<AuthTokenResponse>;
151
177
  /**
152
- * Exchanges an authorization code for an access token.
178
+ * Exchanges a refresh token for a new access token using the OAuth2
179
+ * `refresh_token` grant.
180
+ *
181
+ * Only relevant for OAuth apps that have refresh tokens enabled (the default for
182
+ * new apps). Apps without them never receive a `refreshToken` from
183
+ * {@link getAuthToken}, so they have nothing to pass here — their access tokens
184
+ * are long-lived instead.
185
+ *
186
+ * Omit `clientSecret` for public clients registered with
187
+ * `tokenEndpointAuthMethod: 'none'`.
153
188
  *
154
189
  * @example
155
190
  * ```typescript
156
- * const { accessToken } = await getAuthToken({
191
+ * const { accessToken, refreshToken } = await refreshAuthToken({
157
192
  * clientId: 'your-client-id',
158
193
  * clientSecret: 'your-client-secret',
159
- * code: authCode
194
+ * refreshToken: storedRefreshToken,
160
195
  * })
161
196
  * ```
162
197
  *
163
- * @returns The access token response
164
- * @throws {@link TodoistRequestError} If the token exchange fails
198
+ * @returns The refreshed token. The server rotates the refresh token, so the
199
+ * response carries a new `refreshToken` — persist it in place of the old one.
200
+ * @throws {@link TodoistRequestError} If the refresh fails
165
201
  */
166
- export declare function getAuthToken(args: AuthTokenRequestArgs, options?: AuthOptions): Promise<AuthTokenResponse>;
202
+ export declare function refreshAuthToken(args: RefreshTokenRequestArgs, options?: AuthOptions): Promise<AuthTokenResponse>;
167
203
  /**
168
204
  * Revokes a token using the RFC 7009 OAuth 2.0 Token Revocation standard.
169
205
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/todoist-sdk",
3
- "version": "10.4.1",
3
+ "version": "10.5.0",
4
4
  "description": "The official TypeScript SDK for the Todoist REST API.",
5
5
  "author": "Doist developers",
6
6
  "repository": "https://github.com/Doist/todoist-sdk-typescript",