@ethlete/query 0.1.0-next.16 → 0.1.0-next.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.
@@ -1,5 +1,5 @@
1
- import { getCookie, deleteCookie, setCookie, DestroyService } from '@ethlete/core';
2
- import { Subject, timer, filter, takeWhile, switchMap, of, BehaviorSubject, interval, takeUntil, tap, fromEvent, startWith, concat, take, debounceTime, combineLatest, map, distinctUntilChanged } from 'rxjs';
1
+ import { getCookie, setCookie, deleteCookie, DestroyService } from '@ethlete/core';
2
+ import { filter, takeWhile, switchMap, of, BehaviorSubject, Subject, interval, takeUntil, tap, timer, fromEvent, startWith, concat, take, debounceTime, combineLatest, map, distinctUntilChanged } from 'rxjs';
3
3
  import * as i0 from '@angular/core';
4
4
  import { Directive, Input, InjectionToken, inject, ChangeDetectorRef, ViewContainerRef, TemplateRef, ErrorHandler, ElementRef } from '@angular/core';
5
5
  import { FormGroup } from '@angular/forms';
@@ -11,7 +11,6 @@ class CustomHeaderAuthProvider {
11
11
  }
12
12
  constructor(_config) {
13
13
  this._config = _config;
14
- this.queryClient = null;
15
14
  }
16
15
  cleanUp() {
17
16
  // noop
@@ -27,13 +26,35 @@ class BasicAuthProvider {
27
26
  }
28
27
  constructor(_config) {
29
28
  this._config = _config;
30
- this.queryClient = null;
31
29
  }
32
30
  cleanUp() {
33
31
  // noop
34
32
  }
35
33
  }
36
34
 
35
+ const getOpName = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
36
+ const transformGql = (str) => {
37
+ str = Array.isArray(str) ? str.join('') : str;
38
+ const name = getOpName.exec(str);
39
+ return function (variables) {
40
+ const data = { query: str };
41
+ if (variables)
42
+ data['variables'] = JSON.stringify(variables);
43
+ if (name && name.length) {
44
+ const operationName = name[2];
45
+ if (operationName)
46
+ data['operationName'] = name[2];
47
+ }
48
+ return JSON.stringify(data);
49
+ };
50
+ };
51
+ const gql = (strings, ...values) => {
52
+ const str = strings.reduce((acc, cur, i) => {
53
+ return acc + cur + (values[i] || '');
54
+ }, '');
55
+ return str;
56
+ };
57
+
37
58
  const buildErrorMessage = (code, message) => `[@ethlete\\query:${code}] ${message}`;
38
59
  class QueryError extends Error {
39
60
  constructor(code, message, data) {
@@ -293,171 +314,6 @@ const request = async (options) => {
293
314
  }
294
315
  };
295
316
 
296
- const isBasicAuthProvider = (authProvider) => authProvider instanceof BasicAuthProvider;
297
- const isBearerAuthProvider = (authProvider) => authProvider instanceof BearerAuthProvider;
298
- const isCustomHeaderAuthProvider = (authProvider) => authProvider instanceof CustomHeaderAuthProvider;
299
- const decryptBearer = (token) => {
300
- try {
301
- const base64Url = token.split('.')[1];
302
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
303
- const jsonPayload = decodeURIComponent(atob(base64)
304
- .split('')
305
- .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
306
- .join(''));
307
- return JSON.parse(jsonPayload);
308
- }
309
- catch (error) {
310
- console.error(`Invalid bearer token: ${token}`, error);
311
- return null;
312
- }
313
- };
314
-
315
- class BearerAuthProvider {
316
- get header() {
317
- return { Authorization: `Bearer ${this._token}` };
318
- }
319
- constructor(_config) {
320
- this._config = _config;
321
- this._token = null;
322
- this._refreshToken = null;
323
- this._refreshTimerSubscription = null;
324
- this._failureCount = 0;
325
- this.queryClient = null;
326
- this.onRefreshInitiation$ = new Subject();
327
- this.onRefreshSuccess$ = new Subject();
328
- this.onRefreshFailure$ = new Subject();
329
- this._token = _config.token || null;
330
- this._refreshToken = _config.refreshConfig?.token ?? null;
331
- if (this._config.refreshConfig && this._token) {
332
- this._setupRefresh(this._config.refreshConfig);
333
- }
334
- else if (this._config.refreshConfig?.cookieName) {
335
- const cookie = getCookie(this._config.refreshConfig.cookieName);
336
- if (!cookie) {
337
- this._autoDestruct();
338
- return;
339
- }
340
- this._refreshToken = cookie;
341
- this._refresh(this._config.refreshConfig);
342
- }
343
- else {
344
- this._autoDestruct();
345
- console.error('A BearerAuthProvider was created without token or refresh token. You should provide at least a cookieName where the refresh token might be stored.');
346
- }
347
- }
348
- cleanUp(config) {
349
- this._refreshTimerSubscription?.unsubscribe();
350
- this._refreshTimerSubscription = null;
351
- if (this._config.refreshConfig?.cookieName && !config?.keepCookie) {
352
- deleteCookie(this._config.refreshConfig.cookieName, '/');
353
- }
354
- }
355
- _autoDestruct() {
356
- this.cleanUp();
357
- this.queryClient?.clearAuthProvider();
358
- }
359
- _setupRefresh(config) {
360
- this.cleanUp({ keepCookie: true });
361
- if (!this._token) {
362
- return;
363
- }
364
- const bearer = decryptBearer(this._token);
365
- if (!bearer) {
366
- return;
367
- }
368
- const expiresInPropertyName = config.expiresInPropertyName || 'exp';
369
- const expiresIn = bearer[expiresInPropertyName];
370
- const fiveMinutes = 5 * 60 * 1000;
371
- const refreshBuffer = config.refreshBuffer ?? fiveMinutes;
372
- if (expiresIn === undefined) {
373
- throw new Error(`Bearer token does not contain an '${expiresInPropertyName}' property`);
374
- }
375
- const remainingTime = new Date(expiresIn * 1000).getTime() - refreshBuffer - new Date().getTime();
376
- const strategy = config.strategy ?? "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */;
377
- switch (strategy) {
378
- case "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */:
379
- this._refreshTimerSubscription = timer(remainingTime).subscribe(() => this._refresh(config));
380
- break;
381
- }
382
- }
383
- async _refresh(config) {
384
- if (!this._refreshToken) {
385
- throw new Error('No refresh token found');
386
- }
387
- if (!this.queryClient) {
388
- throw new Error('Query client instance is null');
389
- }
390
- const { method, route, requestAdapter, responseAdapter, paramLocation } = config;
391
- const data = requestAdapter?.(this._refreshToken) ?? { refreshToken: this._refreshToken };
392
- const fullRoute = buildRoute({
393
- base: this.queryClient.config.baseRoute,
394
- route,
395
- queryParams: paramLocation === 'query' ? data : undefined,
396
- });
397
- const requestInit = { body: paramLocation === 'body' ? buildBody(data) : undefined, method };
398
- this.onRefreshInitiation$.next();
399
- try {
400
- const result = await request({
401
- route: fullRoute,
402
- init: requestInit,
403
- });
404
- const tokens = responseAdapter ? responseAdapter(result.data) : result.data;
405
- if (!tokens.token) {
406
- throw new Error('Token not found in response');
407
- }
408
- if (!tokens.refreshToken) {
409
- throw new Error('Refresh token not found in response');
410
- }
411
- this._token = tokens.token;
412
- this._refreshToken = tokens.refreshToken;
413
- this.onRefreshSuccess$.next(tokens);
414
- if (config.cookieName) {
415
- setCookie(config.cookieName, tokens.refreshToken);
416
- }
417
- this._setupRefresh(config);
418
- this._failureCount = 0;
419
- }
420
- catch (error) {
421
- if (error instanceof Error) {
422
- this.onRefreshFailure$.next(await buildRequestError(error, fullRoute, requestInit));
423
- }
424
- else {
425
- this.onRefreshFailure$.next(error);
426
- }
427
- this._failureCount++;
428
- if (this._failureCount <= (config.maxRefreshAttempts ?? 3)) {
429
- this._setupRefresh(config);
430
- }
431
- else {
432
- this._autoDestruct();
433
- }
434
- }
435
- }
436
- }
437
-
438
- const getOpName = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
439
- const transformGql = (str) => {
440
- str = Array.isArray(str) ? str.join('') : str;
441
- const name = getOpName.exec(str);
442
- return function (variables) {
443
- const data = { query: str };
444
- if (variables)
445
- data['variables'] = JSON.stringify(variables);
446
- if (name && name.length) {
447
- const operationName = name[2];
448
- if (operationName)
449
- data['operationName'] = name[2];
450
- }
451
- return JSON.stringify(data);
452
- };
453
- };
454
- const gql = (strings, ...values) => {
455
- const str = strings.reduce((acc, cur, i) => {
456
- return acc + cur + (values[i] || '');
457
- }, '');
458
- return str;
459
- };
460
-
461
317
  function filterSuccess() {
462
318
  return function (source) {
463
319
  return source.pipe(filter((value) => isQueryStateSuccess(value)));
@@ -473,9 +329,14 @@ function filterQueryStates(allowedStates) {
473
329
  return source.pipe(filter((value) => value && allowedStates.includes(value.type)));
474
330
  };
475
331
  }
476
- function takeUntilResponse() {
332
+ function takeUntilResponse(config) {
477
333
  return function (source) {
478
- return source.pipe(takeWhile((value) => isQueryStateLoading(value), true));
334
+ return source.pipe(takeWhile((value) => {
335
+ if (config?.excludeNull && value === null) {
336
+ return false;
337
+ }
338
+ return isQueryStateLoading(value) || value === null;
339
+ }, true));
479
340
  };
480
341
  }
481
342
  function filterNull() {
@@ -717,6 +578,141 @@ class Query {
717
578
  }
718
579
  }
719
580
 
581
+ const isBasicAuthProvider = (authProvider) => authProvider instanceof BasicAuthProvider;
582
+ const isBearerAuthProvider = (authProvider) => authProvider instanceof BearerAuthProvider;
583
+ const isCustomHeaderAuthProvider = (authProvider) => authProvider instanceof CustomHeaderAuthProvider;
584
+ const decryptBearer = (token) => {
585
+ try {
586
+ const base64Url = token.split('.')[1];
587
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
588
+ const jsonPayload = decodeURIComponent(atob(base64)
589
+ .split('')
590
+ .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
591
+ .join(''));
592
+ return JSON.parse(jsonPayload);
593
+ }
594
+ catch (error) {
595
+ console.error(`Invalid bearer token: ${token}`, error);
596
+ return null;
597
+ }
598
+ };
599
+
600
+ class BearerAuthProvider {
601
+ get header() {
602
+ return { Authorization: `Bearer ${this._tokens$.getValue().token}` };
603
+ }
604
+ get tokens$() {
605
+ return this._tokens$.asObservable();
606
+ }
607
+ get tokens() {
608
+ return this._tokens$.getValue();
609
+ }
610
+ get currentRefreshQuery$() {
611
+ return this._currentRefreshQuery$.asObservable();
612
+ }
613
+ get currentRefreshQuery() {
614
+ return this._currentRefreshQuery$.getValue();
615
+ }
616
+ constructor(_config) {
617
+ this._config = _config;
618
+ this._destroy$ = new Subject();
619
+ this._currentRefreshQuery$ = new BehaviorSubject(null);
620
+ this._tokens$ = new BehaviorSubject({
621
+ token: null,
622
+ refreshToken: null,
623
+ });
624
+ const cookieToken = _config.refreshConfig?.cookieName ? getCookie(_config.refreshConfig.cookieName) ?? null : null;
625
+ this._tokens$.next({
626
+ token: _config.token || null,
627
+ refreshToken: _config.refreshConfig?.token || cookieToken || null,
628
+ });
629
+ if (!this.tokens.token && !this.tokens.refreshToken) {
630
+ if (!_config.refreshConfig?.cookieName) {
631
+ console.error('A BearerAuthProvider was created without token or refresh token. You should provide at least a cookieName where the refresh token might be stored.');
632
+ }
633
+ return;
634
+ }
635
+ if (this.tokens.token) {
636
+ this._prepareForRefresh();
637
+ if (this.tokens.refreshToken && this._config.refreshConfig?.cookieName) {
638
+ setCookie(this._config.refreshConfig.cookieName, this.tokens.refreshToken);
639
+ }
640
+ }
641
+ else if (this.tokens.refreshToken) {
642
+ this._refreshQuery();
643
+ }
644
+ }
645
+ cleanUp() {
646
+ this._destroy$.next(true);
647
+ this._destroy$.unsubscribe();
648
+ this._currentRefreshQuery$.next(null);
649
+ if (this._config.refreshConfig?.cookieName) {
650
+ deleteCookie(this._config.refreshConfig.cookieName, '/');
651
+ }
652
+ }
653
+ _prepareForRefresh() {
654
+ if (!this.tokens.token || !this._config.refreshConfig) {
655
+ return;
656
+ }
657
+ const bearer = decryptBearer(this.tokens.token);
658
+ if (!bearer) {
659
+ return;
660
+ }
661
+ const expiresInPropertyName = this._config.refreshConfig.expiresInPropertyName || 'exp';
662
+ const expiresIn = bearer[expiresInPropertyName];
663
+ const fiveMinutes = 5 * 60 * 1000;
664
+ const refreshBuffer = this._config.refreshConfig.refreshBuffer ?? fiveMinutes;
665
+ if (expiresIn === undefined) {
666
+ throw new Error(`Bearer token does not contain an '${expiresInPropertyName}' property`);
667
+ }
668
+ const remainingTime = new Date(expiresIn * 1000).getTime() - refreshBuffer - new Date().getTime();
669
+ const strategy = this._config.refreshConfig.strategy ?? "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */;
670
+ switch (strategy) {
671
+ case "beforeExpiration" /* AuthBearerRefreshStrategy.BeforeExpiration */:
672
+ timer(remainingTime)
673
+ .pipe(takeUntil(this._destroy$))
674
+ .subscribe(() => this._refreshQuery());
675
+ break;
676
+ }
677
+ }
678
+ _refreshQuery() {
679
+ if (!this.tokens.refreshToken || !this._config.refreshConfig) {
680
+ return;
681
+ }
682
+ const args = this._config.refreshConfig.requestArgsAdapter?.(this.tokens) ?? {
683
+ body: { refreshToken: this.tokens.refreshToken },
684
+ };
685
+ const query = this._config.refreshConfig.queryCreator.prepare(args).execute({ skipCache: true });
686
+ this._currentRefreshQuery$.next(query);
687
+ this._currentRefreshQuery$
688
+ .pipe(switchQueryState(), tap((state) => {
689
+ if (isQueryStateSuccess(state)) {
690
+ if (this._config.refreshConfig?.responseAdapter) {
691
+ const tokens = this._config.refreshConfig.responseAdapter(state.response);
692
+ this._tokens$.next(tokens);
693
+ }
694
+ else {
695
+ this._tokens$.next({
696
+ token: state.response['token'],
697
+ refreshToken: state.response['refreshToken'],
698
+ });
699
+ }
700
+ if (this._config.refreshConfig?.cookieName && this.tokens.refreshToken) {
701
+ setCookie(this._config.refreshConfig.cookieName, this.tokens.refreshToken);
702
+ }
703
+ this._prepareForRefresh();
704
+ }
705
+ else if (isQueryStateFailure(state)) {
706
+ this._tokens$.next({ token: null, refreshToken: null });
707
+ if (this._config.refreshConfig?.cookieName) {
708
+ deleteCookie(this._config.refreshConfig.cookieName, '/');
709
+ }
710
+ }
711
+ }), takeUntilResponse(), takeUntil(this._destroy$))
712
+ .subscribe();
713
+ }
714
+ }
715
+
720
716
  class QueryDirective {
721
717
  get query() {
722
718
  return this._query;
@@ -1328,7 +1324,6 @@ class QueryClient {
1328
1324
  throw new Error('The auth provider is already set. Please call clearAuthProvider() first.');
1329
1325
  }
1330
1326
  this._authProvider$.next(authProvider);
1331
- authProvider.queryClient = this;
1332
1327
  };
1333
1328
  this.clearAuthProvider = () => {
1334
1329
  this.authProvider?.cleanUp();