@emilgroup/migration-sdk 0.10.1-beta.14 → 0.10.1-beta.18

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
@@ -17,11 +17,11 @@ Although this package can be used in both TypeScript and JavaScript, it is inten
17
17
  Navigate to the folder of your consuming project and run one of the following commands:
18
18
 
19
19
  ```
20
- npm install @emilgroup/migration-sdk@0.10.1-beta.14 --save
20
+ npm install @emilgroup/migration-sdk@0.10.1-beta.18 --save
21
21
  ```
22
22
  or
23
23
  ```
24
- yarn add @emilgroup/migration-sdk@0.10.1-beta.14
24
+ yarn add @emilgroup/migration-sdk@0.10.1-beta.18
25
25
  ```
26
26
 
27
27
  And then you can import ``.
@@ -101,8 +101,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
101
101
  // http bearer authentication required
102
102
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
103
103
 
104
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
105
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
104
+ // Only Authorization falls back to the configured token; an empty value is never sent.
105
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
106
+ if (authorizationValue) {
107
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
106
108
  }
107
109
 
108
110
 
@@ -152,8 +154,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
152
154
  // http bearer authentication required
153
155
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
154
156
 
155
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
156
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
157
+ // Only Authorization falls back to the configured token; an empty value is never sent.
158
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
159
+ if (authorizationValue) {
160
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
157
161
  }
158
162
 
159
163
 
@@ -199,8 +203,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
199
203
  // http bearer authentication required
200
204
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
201
205
 
202
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
203
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
206
+ // Only Authorization falls back to the configured token; an empty value is never sent.
207
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
208
+ if (authorizationValue) {
209
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
204
210
  }
205
211
 
206
212
 
@@ -246,8 +252,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
246
252
  // http bearer authentication required
247
253
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
248
254
 
249
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
250
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
255
+ // Only Authorization falls back to the configured token; an empty value is never sent.
256
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
257
+ if (authorizationValue) {
258
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
251
259
  }
252
260
 
253
261
 
@@ -293,8 +301,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
293
301
  // http bearer authentication required
294
302
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
295
303
 
296
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
297
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
304
+ // Only Authorization falls back to the configured token; an empty value is never sent.
305
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
306
+ if (authorizationValue) {
307
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
298
308
  }
299
309
 
300
310
 
@@ -340,8 +350,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
340
350
  // http bearer authentication required
341
351
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
342
352
 
343
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
344
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
353
+ // Only Authorization falls back to the configured token; an empty value is never sent.
354
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
355
+ if (authorizationValue) {
356
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
345
357
  }
346
358
 
347
359
 
@@ -388,8 +400,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
388
400
  // http bearer authentication required
389
401
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
390
402
 
391
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
392
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
403
+ // Only Authorization falls back to the configured token; an empty value is never sent.
404
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
405
+ if (authorizationValue) {
406
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
393
407
  }
394
408
 
395
409
 
@@ -433,8 +447,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
433
447
  // http bearer authentication required
434
448
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
435
449
 
436
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
437
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
450
+ // Only Authorization falls back to the configured token; an empty value is never sent.
451
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
452
+ if (authorizationValue) {
453
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
438
454
  }
439
455
 
440
456
 
@@ -478,8 +494,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
478
494
  // http bearer authentication required
479
495
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
480
496
 
481
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
482
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
497
+ // Only Authorization falls back to the configured token; an empty value is never sent.
498
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
499
+ if (authorizationValue) {
500
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
483
501
  }
484
502
 
485
503
 
@@ -554,8 +572,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
554
572
  localVarQueryParameter['filters'] = filters;
555
573
  }
556
574
 
557
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
558
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
575
+ // Only Authorization falls back to the configured token; an empty value is never sent.
576
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
577
+ if (authorizationValue) {
578
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
559
579
  }
560
580
 
561
581
 
@@ -630,8 +650,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
630
650
  localVarQueryParameter['filters'] = filters;
631
651
  }
632
652
 
633
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
634
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
653
+ // Only Authorization falls back to the configured token; an empty value is never sent.
654
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
655
+ if (authorizationValue) {
656
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
635
657
  }
636
658
 
637
659
 
@@ -710,8 +732,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
710
732
  localVarQueryParameter['filters'] = filters;
711
733
  }
712
734
 
713
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
714
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
735
+ // Only Authorization falls back to the configured token; an empty value is never sent.
736
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
737
+ if (authorizationValue) {
738
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
715
739
  }
716
740
 
717
741
 
@@ -790,8 +814,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
790
814
  localVarQueryParameter['filters'] = filters;
791
815
  }
792
816
 
793
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
794
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
817
+ // Only Authorization falls back to the configured token; an empty value is never sent.
818
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
819
+ if (authorizationValue) {
820
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
795
821
  }
796
822
 
797
823
 
@@ -866,8 +892,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
866
892
  localVarQueryParameter['filters'] = filters;
867
893
  }
868
894
 
869
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
870
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
895
+ // Only Authorization falls back to the configured token; an empty value is never sent.
896
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
897
+ if (authorizationValue) {
898
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
871
899
  }
872
900
 
873
901
 
@@ -914,8 +942,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
914
942
  // http bearer authentication required
915
943
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
916
944
 
917
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
918
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
945
+ // Only Authorization falls back to the configured token; an empty value is never sent.
946
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
947
+ if (authorizationValue) {
948
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
919
949
  }
920
950
 
921
951
 
@@ -965,8 +995,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
965
995
  // http bearer authentication required
966
996
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
967
997
 
968
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
969
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
998
+ // Only Authorization falls back to the configured token; an empty value is never sent.
999
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
1000
+ if (authorizationValue) {
1001
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
970
1002
  }
971
1003
 
972
1004
 
@@ -1016,8 +1048,10 @@ export const MigrationApiAxiosParamCreator = function (configuration?: Configura
1016
1048
  // http bearer authentication required
1017
1049
  await setBearerAuthToObject(localVarHeaderParameter, configuration)
1018
1050
 
1019
- if (authorization !== undefined && authorization !== null || baseAccessToken !== undefined && baseAccessToken !== null) {
1020
- localVarHeaderParameter['Authorization'] = String(authorization ? authorization : baseAccessToken);
1051
+ // Only Authorization falls back to the configured token; an empty value is never sent.
1052
+ const authorizationValue = 'Authorization'.toLowerCase() === 'authorization' ? (authorization || baseAccessToken) : authorization;
1053
+ if (authorizationValue) {
1054
+ localVarHeaderParameter['Authorization'] = String(authorizationValue);
1021
1055
  }
1022
1056
 
1023
1057
 
package/base.ts CHANGED
@@ -32,9 +32,20 @@ export const COLLECTION_FORMATS = {
32
32
  pipes: "|",
33
33
  };
34
34
 
35
+ export interface SessionClass {
36
+ sub: string;
37
+ username?: string;
38
+ email?: string;
39
+ tenantSlug?: string;
40
+ permissions?: string;
41
+ expiresAt?: number;
42
+ }
43
+
35
44
  export interface LoginClass {
36
45
  accessToken: string;
37
46
  permissions: string;
47
+ authMode?: string;
48
+ session?: SessionClass;
38
49
  }
39
50
 
40
51
 
@@ -68,10 +79,19 @@ interface TokenData {
68
79
  accessToken?: string;
69
80
  username?: string;
70
81
  permissions?: string;
82
+ // 'both' or 'session' when authservice keeps the tokens in a server session.
83
+ authMode?: string;
84
+ sub?: string;
71
85
  }
72
86
 
73
87
  const NETWORK_ERROR_MESSAGE = "Network Error";
74
88
  const TOKEN_DATA = 'APP_TOKEN';
89
+ // Sent only to authservice, to ask for a server session.
90
+ const AUTH_MODE_HEADER = 'X-Emil-Auth-Mode';
91
+ // Names the account on every call; one browser session can hold several.
92
+ const ACCOUNT_HEADER = 'X-Emil-Account';
93
+ const SESSION_MODES = ['both', 'session'];
94
+ const RESEND_SAFE_METHODS = ['get', 'head', 'options'];
75
95
 
76
96
  /**
77
97
  *
@@ -94,7 +114,11 @@ export class BaseAPI {
94
114
  this.basePath = configuration.basePath || this.basePath;
95
115
 
96
116
  if (configuration.cookieAuth) {
97
- this.useCookieAuth();
117
+ this.configuration.username = this.configuration.username || this.tokenData.username;
118
+ }
119
+
120
+ if (this.usesSession()) {
121
+ this.useSessionAuth();
98
122
  } else {
99
123
  // Use config token if provided, otherwise use tokenData token
100
124
  const configToken = this.configuration.accessToken;
@@ -114,10 +138,105 @@ export class BaseAPI {
114
138
  this.attachInterceptor(axios);
115
139
  }
116
140
 
117
- // Cookie mode: the browser sends the HttpOnly access cookie, so no token is read from storage.
118
- private useCookieAuth() {
119
- this.configuration.username = this.configuration.username || this.tokenData.username;
120
- this.configuration.baseOptions = { ...this.configuration.baseOptions, withCredentials: true };
141
+ // The app's own account wins over APP_TOKEN, which holds whichever tab wrote last.
142
+ private sessionAccount(): string | undefined {
143
+ return this.configuration.account || this.tokenData.sub;
144
+ }
145
+
146
+ // Apps may pass the address as the second argument only; authservice is on the same host.
147
+ private authBasePath(): string {
148
+ return this.configuration.basePath || this.basePath;
149
+ }
150
+
151
+ private usesSession(): boolean {
152
+ return !!this.configuration.cookieAuth && SESSION_MODES.indexOf(this.tokenData.authMode) !== -1 && !!this.sessionAccount();
153
+ }
154
+
155
+ private useSessionAuth() {
156
+ const { headers, ...baseOptions } = this.configuration.baseOptions || {};
157
+ this.configuration.accessToken = '';
158
+ this.configuration.baseOptions = {
159
+ ...baseOptions,
160
+ withCredentials: true,
161
+ headers: { ...headers, [ACCOUNT_HEADER]: this.sessionAccount() },
162
+ };
163
+ }
164
+
165
+ private useHeaderAuth(accessToken: string) {
166
+ this.configuration.accessToken = accessToken ? `Bearer ${accessToken}` : '';
167
+ this.tokenData.accessToken = accessToken;
168
+ delete this.tokenData.authMode;
169
+ delete this.tokenData.sub;
170
+ if (this.configuration.cookieAuth) {
171
+ const { withCredentials, headers, ...baseOptions } = this.configuration.baseOptions || {};
172
+ const { [ACCOUNT_HEADER]: account, ...otherHeaders } = headers || {};
173
+ this.configuration.baseOptions = { ...baseOptions, headers: otherHeaders };
174
+ }
175
+ }
176
+
177
+ private applyTokenReply({ accessToken, authMode, session }: LoginClass) {
178
+ if (this.configuration.cookieAuth && SESSION_MODES.indexOf(authMode) !== -1 && session?.sub) {
179
+ this.tokenData.authMode = authMode;
180
+ this.tokenData.sub = session.sub;
181
+ if (this.configuration.account) {
182
+ // A workspace switch moves the app to another account.
183
+ this.configuration.account = session.sub;
184
+ }
185
+ // Both keeps the token for apps not moved yet; session mode keeps no token at all.
186
+ if (authMode === 'both' && accessToken) {
187
+ this.tokenData.accessToken = accessToken;
188
+ } else {
189
+ delete this.tokenData.accessToken;
190
+ }
191
+ this.useSessionAuth();
192
+ } else {
193
+ this.useHeaderAuth(accessToken);
194
+ }
195
+ }
196
+
197
+ private sessionHeaders(): Record<string, string> {
198
+ if (!this.configuration.cookieAuth) {
199
+ return {};
200
+ }
201
+ return {
202
+ [AUTH_MODE_HEADER]: 'session',
203
+ ...(this.usesSession() && { [ACCOUNT_HEADER]: this.sessionAccount() }),
204
+ };
205
+ }
206
+
207
+ private withCurrentAuth(config: AxiosRequestConfig): AxiosRequestConfig {
208
+ delete config.headers['Authorization'];
209
+ delete config.headers[ACCOUNT_HEADER];
210
+ if (this.usesSession()) {
211
+ config.headers[ACCOUNT_HEADER] = this.sessionAccount();
212
+ config.withCredentials = true;
213
+ } else if (this.configuration.accessToken) {
214
+ config.headers['Authorization'] = this.configuration.accessToken;
215
+ }
216
+ return config;
217
+ }
218
+
219
+ // Both keeps the token, so a session call the browser could not complete can be sent again with it.
220
+ private canRetryWithToken(config: AxiosRequestConfig): boolean {
221
+ return !!config.withCredentials && !!config.headers?.[ACCOUNT_HEADER]
222
+ && !!this.tokenData.accessToken && this.tokenData.authMode !== 'session';
223
+ }
224
+
225
+ // A network error can come after the server ran the call, so only a call that is safe to repeat is sent again.
226
+ private canResend(config: AxiosRequestConfig): boolean {
227
+ return RESEND_SAFE_METHODS.indexOf((config.method || 'get').toLowerCase()) !== -1
228
+ || Object.keys(config.headers || {}).some((name) => name.toLowerCase() === 'idempotency-key');
229
+ }
230
+
231
+ private switchToToken() {
232
+ this.useHeaderAuth(this.tokenData.accessToken);
233
+ this.storeTokenData(this.tokenData);
234
+ }
235
+
236
+ private retryWithToken(axios: AxiosInstance, config: AxiosRequestConfig) {
237
+ this.switchToToken();
238
+ config.withCredentials = false;
239
+ return axios(this.withCurrentAuth(config));
121
240
  }
122
241
 
123
242
  selectEnvironment(env: Environment) {
@@ -139,8 +258,8 @@ export class BaseAPI {
139
258
  async authorize(username: string, password: string, targetWorkspace?: string): Promise<void> {
140
259
  const options: AxiosRequestConfig = {
141
260
  method: 'POST',
142
- url: `${this.configuration.basePath}/authservice/v1/login`,
143
- headers: { 'Content-Type': 'application/json' },
261
+ url: `${this.authBasePath()}/authservice/v1/login`,
262
+ headers: { 'Content-Type': 'application/json', ...(this.configuration.cookieAuth && { [AUTH_MODE_HEADER]: 'session' }) },
144
263
  data: {
145
264
  username,
146
265
  password,
@@ -150,15 +269,10 @@ export class BaseAPI {
150
269
 
151
270
  const response = await globalAxios.request<LoginClass>(options);
152
271
 
153
- const { data: { accessToken, permissions } } = response;
154
-
155
272
  this.configuration.username = username;
156
273
  this.tokenData.username = username;
157
- this.tokenData.permissions = permissions;
158
- if (!this.configuration.cookieAuth) {
159
- this.configuration.accessToken = `Bearer ${accessToken}`;
160
- this.tokenData.accessToken = accessToken;
161
- }
274
+ this.tokenData.permissions = response.data.permissions;
275
+ this.applyTokenReply(response.data);
162
276
 
163
277
  }
164
278
 
@@ -182,9 +296,10 @@ export class BaseAPI {
182
296
 
183
297
  const options: AxiosRequestConfig = {
184
298
  method: 'POST',
185
- url: `${this.configuration.basePath}/authservice/v1/refresh-token`,
299
+ url: `${this.authBasePath()}/authservice/v1/refresh-token`,
186
300
  headers: {
187
301
  'Content-Type': 'application/json',
302
+ ...this.sessionHeaders(),
188
303
  },
189
304
  data: { username: username },
190
305
  withCredentials: true,
@@ -196,11 +311,6 @@ export class BaseAPI {
196
311
 
197
312
  private storeTokenData(tokenData?: TokenData) {
198
313
  if (typeof window !== 'undefined') {
199
- if (tokenData && this.configuration?.cookieAuth) {
200
- // Cookie mode saves who is logged in, never the token; a token saved by apps not yet migrated stays.
201
- const { username, permissions } = tokenData;
202
- tokenData = { ...(defaultStorage().get<TokenData>(TOKEN_DATA) || {}), username, permissions };
203
- }
204
314
  defaultStorage().set<TokenData>(TOKEN_DATA, tokenData);
205
315
  }
206
316
  }
@@ -230,22 +340,22 @@ export class BaseAPI {
230
340
  && !originalConfig._retry) {
231
341
  originalConfig._retry = true;
232
342
  try {
233
- const { accessToken: tokenString, permissions } = await this.refreshTokenInternal();
234
- this.tokenData.permissions = permissions;
235
-
236
- delete originalConfig.headers['Authorization']
343
+ const reply = await this.refreshTokenInternal();
344
+ this.tokenData.permissions = reply.permissions;
345
+ this.applyTokenReply(reply);
346
+ this.storeTokenData(this.tokenData);
237
347
 
238
- // In cookie mode the refresh reply renewed the access cookie, so retry without a header.
239
- if (!this.configuration.cookieAuth) {
240
- const accessToken = `Bearer ${tokenString}`;
241
- originalConfig.headers['Authorization'] = accessToken;
242
- this.configuration.accessToken = accessToken;
243
- this.tokenData.accessToken = tokenString;
348
+ if (!this.usesSession()) {
349
+ return axios(this.withCurrentAuth(originalConfig));
244
350
  }
245
351
 
246
- this.storeTokenData(this.tokenData);
247
-
248
- return axios(originalConfig);
352
+ // Both mode: if the browser does not send the session cookie (another site), use the token.
353
+ return axios(this.withCurrentAuth(originalConfig)).catch((retryError) => {
354
+ if (retryError?.response?.status !== 401 || !this.tokenData.accessToken) {
355
+ return Promise.reject(retryError);
356
+ }
357
+ return this.retryWithToken(axios, originalConfig);
358
+ });
249
359
  } catch (_error) {
250
360
  if (_error.response && _error.response.data) {
251
361
  return Promise.reject(_error.response.data);
@@ -253,26 +363,30 @@ export class BaseAPI {
253
363
  return Promise.reject(_error);
254
364
  }
255
365
  }
366
+ } else if (err.message === NETWORK_ERROR_MESSAGE && this.canRetryWithToken(originalConfig)) {
367
+ // Both mode: the browser blocked the session call or hid its reply; later calls use the token.
368
+ if (!this.canResend(originalConfig)) {
369
+ this.switchToToken();
370
+ return Promise.reject(err);
371
+ }
372
+ return this.retryWithToken(axios, originalConfig);
256
373
  } else if (err.message === NETWORK_ERROR_MESSAGE
257
- && originalConfig.headers.hasOwnProperty('Authorization')
374
+ && (originalConfig.headers.hasOwnProperty('Authorization')
375
+ || (this.configuration.cookieAuth && originalConfig.withCredentials && this.canResend(originalConfig)))
258
376
  && _retry_count < 4
259
377
  ) {
260
378
  _retry_count++;
261
379
  try {
262
- const { accessToken: tokenString, permissions } = await this.refreshTokenInternal();
263
- const accessToken = `Bearer ${tokenString}`;
264
- this.tokenData.permissions = permissions;
380
+ // In session mode an unreadable 401 (no CORS headers on the error reply) also lands here.
381
+ const reply = await this.refreshTokenInternal();
382
+ this.tokenData.permissions = reply.permissions;
265
383
 
266
384
  _retry = true;
267
- originalConfig.headers['Authorization'] = accessToken;
268
-
269
- this.configuration.accessToken = accessToken;
270
- this.tokenData.accessToken = tokenString;
271
-
385
+ this.applyTokenReply(reply);
272
386
  this.storeTokenData(this.tokenData);
273
387
 
274
388
  return axios.request({
275
- ...originalConfig,
389
+ ...this.withCurrentAuth(originalConfig),
276
390
  });
277
391
  } catch (_error) {
278
392
  if (_error.response && _error.response.data) {
package/common.ts CHANGED
@@ -130,9 +130,10 @@ export const toPathString = function (url: URL) {
130
130
  * @export
131
131
  */
132
132
  export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
133
- return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
133
+ // Typed by hand: since axios 1.20, request() returns an internal axios type that SDK declarations cannot name.
134
+ return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH): Promise<R> => {
134
135
  const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};
135
- return axios.request<T, R>(axiosRequestArgs);
136
+ return axios.request<T, R>(axiosRequestArgs) as Promise<R>;
136
137
  };
137
138
  }
138
139
 
package/configuration.ts CHANGED
@@ -22,6 +22,7 @@ export interface ConfigurationParameters {
22
22
  baseOptions?: any;
23
23
  formDataCtor?: new () => any;
24
24
  cookieAuth?: boolean;
25
+ account?: string;
25
26
  }
26
27
 
27
28
  export class Configuration {
@@ -76,13 +77,20 @@ export class Configuration {
76
77
  formDataCtor?: new () => any;
77
78
 
78
79
  /**
79
- * Authenticate with the HttpOnly access cookie instead of a stored token: every call sends credentials.
80
- * The browser holds one cookie, so use it with one logged-in account; log out through the auth service.
80
+ * Use a server session when authservice makes one; otherwise the token, as without this option.
81
+ * Set `account` too, so each SDK object keeps its own account.
81
82
  *
82
83
  * @type {boolean}
83
84
  * @memberof Configuration
84
85
  */
85
86
  cookieAuth?: boolean;
87
+ /**
88
+ * The session account (Cognito sub) this app shows, so the SDK never takes another tab's account.
89
+ *
90
+ * @type {string}
91
+ * @memberof Configuration
92
+ */
93
+ account?: string;
86
94
 
87
95
  constructor(param: ConfigurationParameters = {}) {
88
96
  this.apiKey = param.apiKey;
@@ -93,6 +101,7 @@ export class Configuration {
93
101
  this.baseOptions = param.baseOptions;
94
102
  this.formDataCtor = param.formDataCtor;
95
103
  this.cookieAuth = param.cookieAuth;
104
+ this.account = param.account;
96
105
  }
97
106
 
98
107
  /**
@@ -66,5 +66,5 @@ export declare class HealthApi extends BaseAPI {
66
66
  * @throws {RequiredError}
67
67
  * @memberof HealthApi
68
68
  */
69
- check(options?: AxiosRequestConfig): Promise<import("axios").AxiosResponse<InlineResponse200, any, {}>>;
69
+ check(options?: AxiosRequestConfig): Promise<import("axios").AxiosResponse<InlineResponse200, any, {}, any>>;
70
70
  }