@ethlete/query 5.15.0 → 5.16.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/.eslintrc.json +40 -0
- package/CHANGELOG.md +14 -0
- package/jest.config.ts +23 -0
- package/ng-package.json +8 -0
- package/package.json +6 -23
- package/project.json +37 -0
- package/{index.d.ts → src/index.ts} +2 -0
- package/src/lib/auth/auth-provider.types.ts +160 -0
- package/src/lib/auth/auth-provider.utils.ts +39 -0
- package/src/lib/auth/basic-auth-provider.ts +17 -0
- package/src/lib/auth/bearer-auth-provider.ts +243 -0
- package/src/lib/auth/custom-header-auth-provider.ts +13 -0
- package/{lib/class-validator/class-validator.types.d.ts → src/lib/class-validator/class-validator.types.ts} +4 -3
- package/src/lib/class-validator/class-validator.utils.ts +5 -0
- package/src/lib/devtools/components/query-devtools/query-devtools.component.html +402 -0
- package/src/lib/devtools/components/query-devtools/query-devtools.component.scss +360 -0
- package/src/lib/devtools/components/query-devtools/query-devtools.component.ts +427 -0
- package/src/lib/devtools/pipes/query-short-name.pipe.ts +24 -0
- package/src/lib/devtools/utils/query-client.utils.ts +25 -0
- package/src/lib/directives/infinity-query-trigger.directive.ts +58 -0
- package/src/lib/directives/infinity-query.directive.ts +208 -0
- package/src/lib/directives/query.directive.ts +201 -0
- package/src/lib/entity/entity-store.ts +180 -0
- package/src/lib/entity/entity.types.ts +34 -0
- package/src/lib/entity/entity.utils.ts +51 -0
- package/src/lib/gql/gql.ts +22 -0
- package/src/lib/infinite-query/infinity-query.ts +245 -0
- package/src/lib/infinite-query/infinity-query.types.ts +149 -0
- package/src/lib/infinite-query/infinity-query.utils.ts +14 -0
- package/src/lib/logger/logger.ts +30 -0
- package/src/lib/query/query.ts +631 -0
- package/src/lib/query/query.types.ts +532 -0
- package/src/lib/query/query.utils.spec.ts +92 -0
- package/src/lib/query/query.utils.ts +450 -0
- package/src/lib/query-client/query-client.spec.ts +77 -0
- package/src/lib/query-client/query-client.ts +195 -0
- package/src/lib/query-client/query-client.types.ts +101 -0
- package/src/lib/query-client/query-client.utils.spec.ts +61 -0
- package/src/lib/query-client/query-client.utils.ts +28 -0
- package/src/lib/query-creator/query-creator.ts +103 -0
- package/src/lib/query-creator/query-creator.types.ts +74 -0
- package/src/lib/query-form/query-form.ts +526 -0
- package/src/lib/query-form/query-form.types.ts +169 -0
- package/src/lib/query-form/query-form.utils.ts +116 -0
- package/src/lib/query-store/query-store.ts +192 -0
- package/src/lib/request/request.ts +253 -0
- package/src/lib/request/request.types.ts +164 -0
- package/src/lib/request/request.util.spec.ts +177 -0
- package/src/lib/request/request.util.ts +399 -0
- package/src/lib/stories/angular-example-page.mdx +85 -0
- package/src/lib/stories/authentication-page.mdx +35 -0
- package/src/lib/stories/changelog-page.mdx +9 -0
- package/src/lib/stories/client-page.mdx +100 -0
- package/src/lib/stories/queries-page.mdx +117 -0
- package/src/lib/symfony/symfony.types.ts +21 -0
- package/src/lib/symfony/symfony.utils.ts +43 -0
- package/src/lib/utils/data.utils.ts +280 -0
- package/src/lib/utils/deep-freeze.util.ts +21 -0
- package/{lib/utils/def.util.d.ts → src/lib/utils/def.util.ts} +1 -1
- package/src/test-setup.ts +1 -0
- package/tsconfig.json +29 -0
- package/tsconfig.lib.json +12 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +11 -0
- package/esm2022/ethlete-query.mjs +0 -5
- package/esm2022/index.mjs +0 -20
- package/esm2022/lib/auth/auth-provider.types.mjs +0 -2
- package/esm2022/lib/auth/auth-provider.utils.mjs +0 -25
- package/esm2022/lib/auth/basic-auth-provider.mjs +0 -15
- package/esm2022/lib/auth/bearer-auth-provider.mjs +0 -168
- package/esm2022/lib/auth/custom-header-auth-provider.mjs +0 -12
- package/esm2022/lib/auth/index.mjs +0 -6
- package/esm2022/lib/class-validator/class-validator.types.mjs +0 -2
- package/esm2022/lib/class-validator/class-validator.utils.mjs +0 -4
- package/esm2022/lib/class-validator/index.mjs +0 -3
- package/esm2022/lib/devtools/components/query-devtools/index.mjs +0 -2
- package/esm2022/lib/devtools/components/query-devtools/query-devtools.component.mjs +0 -297
- package/esm2022/lib/devtools/pipes/index.mjs +0 -2
- package/esm2022/lib/devtools/pipes/query-short-name.pipe.mjs +0 -24
- package/esm2022/lib/devtools/utils/index.mjs +0 -2
- package/esm2022/lib/devtools/utils/query-client.utils.mjs +0 -13
- package/esm2022/lib/directives/index.mjs +0 -4
- package/esm2022/lib/directives/infinity-query-trigger.directive.mjs +0 -56
- package/esm2022/lib/directives/infinity-query.directive.mjs +0 -147
- package/esm2022/lib/directives/query.directive.mjs +0 -123
- package/esm2022/lib/entity/entity-store.mjs +0 -127
- package/esm2022/lib/entity/entity.types.mjs +0 -2
- package/esm2022/lib/entity/entity.utils.mjs +0 -29
- package/esm2022/lib/entity/index.mjs +0 -4
- package/esm2022/lib/gql/gql.mjs +0 -23
- package/esm2022/lib/gql/index.mjs +0 -2
- package/esm2022/lib/infinite-query/index.mjs +0 -4
- package/esm2022/lib/infinite-query/infinity-query.mjs +0 -171
- package/esm2022/lib/infinite-query/infinity-query.types.mjs +0 -2
- package/esm2022/lib/infinite-query/infinity-query.utils.mjs +0 -3
- package/esm2022/lib/logger/index.mjs +0 -2
- package/esm2022/lib/logger/logger.mjs +0 -16
- package/esm2022/lib/query/index.mjs +0 -4
- package/esm2022/lib/query/query.mjs +0 -428
- package/esm2022/lib/query/query.types.mjs +0 -2
- package/esm2022/lib/query/query.utils.mjs +0 -253
- package/esm2022/lib/query-client/index.mjs +0 -4
- package/esm2022/lib/query-client/query-client.mjs +0 -80
- package/esm2022/lib/query-client/query-client.types.mjs +0 -2
- package/esm2022/lib/query-client/query-client.utils.mjs +0 -20
- package/esm2022/lib/query-creator/index.mjs +0 -3
- package/esm2022/lib/query-creator/query-creator.mjs +0 -55
- package/esm2022/lib/query-creator/query-creator.types.mjs +0 -2
- package/esm2022/lib/query-form/index.mjs +0 -4
- package/esm2022/lib/query-form/query-form.mjs +0 -368
- package/esm2022/lib/query-form/query-form.types.mjs +0 -2
- package/esm2022/lib/query-form/query-form.utils.mjs +0 -97
- package/esm2022/lib/query-store/index.mjs +0 -2
- package/esm2022/lib/query-store/query-store.mjs +0 -144
- package/esm2022/lib/request/index.mjs +0 -4
- package/esm2022/lib/request/request.mjs +0 -196
- package/esm2022/lib/request/request.types.mjs +0 -2
- package/esm2022/lib/request/request.util.mjs +0 -252
- package/esm2022/lib/symfony/index.mjs +0 -3
- package/esm2022/lib/symfony/symfony.types.mjs +0 -2
- package/esm2022/lib/symfony/symfony.utils.mjs +0 -31
- package/esm2022/lib/utils/data.utils.mjs +0 -138
- package/esm2022/lib/utils/deep-freeze.util.mjs +0 -17
- package/esm2022/lib/utils/def.util.mjs +0 -8
- package/esm2022/lib/utils/index.mjs +0 -4
- package/fesm2022/ethlete-query.mjs +0 -3276
- package/fesm2022/ethlete-query.mjs.map +0 -1
- package/lib/auth/auth-provider.types.d.ts +0 -133
- package/lib/auth/auth-provider.utils.d.ts +0 -9
- package/lib/auth/basic-auth-provider.d.ts +0 -10
- package/lib/auth/bearer-auth-provider.d.ts +0 -23
- package/lib/auth/custom-header-auth-provider.d.ts +0 -9
- package/lib/class-validator/class-validator.utils.d.ts +0 -2
- package/lib/devtools/components/query-devtools/query-devtools.component.d.ts +0 -151
- package/lib/devtools/pipes/query-short-name.pipe.d.ts +0 -8
- package/lib/devtools/utils/query-client.utils.d.ts +0 -12
- package/lib/directives/infinity-query-trigger.directive.d.ts +0 -15
- package/lib/directives/infinity-query.directive.d.ts +0 -49
- package/lib/directives/query.directive.d.ts +0 -63
- package/lib/entity/entity-store.d.ts +0 -25
- package/lib/entity/entity.types.d.ts +0 -25
- package/lib/entity/entity.utils.d.ts +0 -12
- package/lib/gql/gql.d.ts +0 -6
- package/lib/infinite-query/infinity-query.d.ts +0 -29
- package/lib/infinite-query/infinity-query.types.d.ts +0 -104
- package/lib/infinite-query/infinity-query.utils.d.ts +0 -4
- package/lib/logger/logger.d.ts +0 -12
- package/lib/query/query.d.ts +0 -60
- package/lib/query/query.types.d.ts +0 -388
- package/lib/query/query.utils.d.ts +0 -91
- package/lib/query-client/query-client.d.ts +0 -29
- package/lib/query-client/query-client.types.d.ts +0 -82
- package/lib/query-client/query-client.utils.d.ts +0 -4
- package/lib/query-creator/query-creator.d.ts +0 -20
- package/lib/query-creator/query-creator.types.d.ts +0 -13
- package/lib/query-form/query-form.d.ts +0 -89
- package/lib/query-form/query-form.types.d.ts +0 -138
- package/lib/query-form/query-form.utils.d.ts +0 -13
- package/lib/query-store/query-store.d.ts +0 -31
- package/lib/request/request.d.ts +0 -3
- package/lib/request/request.types.d.ts +0 -126
- package/lib/request/request.util.d.ts +0 -92
- package/lib/symfony/symfony.types.d.ts +0 -19
- package/lib/symfony/symfony.utils.d.ts +0 -6
- package/lib/utils/data.utils.d.ts +0 -59
- package/lib/utils/deep-freeze.util.d.ts +0 -1
- /package/{lib/auth/index.d.ts → src/lib/auth/index.ts} +0 -0
- /package/{lib/class-validator/index.d.ts → src/lib/class-validator/index.ts} +0 -0
- /package/{lib/devtools/components/query-devtools/index.d.ts → src/lib/devtools/components/query-devtools/index.ts} +0 -0
- /package/{lib/devtools/pipes/index.d.ts → src/lib/devtools/pipes/index.ts} +0 -0
- /package/{lib/devtools/utils/index.d.ts → src/lib/devtools/utils/index.ts} +0 -0
- /package/{lib/directives/index.d.ts → src/lib/directives/index.ts} +0 -0
- /package/{lib/entity/index.d.ts → src/lib/entity/index.ts} +0 -0
- /package/{lib/gql/index.d.ts → src/lib/gql/index.ts} +0 -0
- /package/{lib/infinite-query/index.d.ts → src/lib/infinite-query/index.ts} +0 -0
- /package/{lib/logger/index.d.ts → src/lib/logger/index.ts} +0 -0
- /package/{lib/query/index.d.ts → src/lib/query/index.ts} +0 -0
- /package/{lib/query-client/index.d.ts → src/lib/query-client/index.ts} +0 -0
- /package/{lib/query-creator/index.d.ts → src/lib/query-creator/index.ts} +0 -0
- /package/{lib/query-form/index.d.ts → src/lib/query-form/index.ts} +0 -0
- /package/{lib/query-store/index.d.ts → src/lib/query-store/index.ts} +0 -0
- /package/{lib/request/index.d.ts → src/lib/request/index.ts} +0 -0
- /package/{lib/symfony/index.d.ts → src/lib/symfony/index.ts} +0 -0
- /package/{lib/utils/index.d.ts → src/lib/utils/index.ts} +0 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["../../.eslintrc.json"],
|
|
3
|
+
"ignorePatterns": ["!**/*"],
|
|
4
|
+
"overrides": [
|
|
5
|
+
{
|
|
6
|
+
"files": ["*.ts"],
|
|
7
|
+
"extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"],
|
|
8
|
+
"rules": {
|
|
9
|
+
"@angular-eslint/directive-selector": [
|
|
10
|
+
"error",
|
|
11
|
+
{
|
|
12
|
+
"type": "attribute",
|
|
13
|
+
"prefix": "et"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"@angular-eslint/component-selector": [
|
|
17
|
+
"error",
|
|
18
|
+
{
|
|
19
|
+
"type": ["element", "attribute"],
|
|
20
|
+
"prefix": "et",
|
|
21
|
+
"style": "kebab-case"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"@angular-eslint/no-host-metadata-property": "off"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"files": ["*.html"],
|
|
29
|
+
"extends": ["plugin:@nx/angular-template"],
|
|
30
|
+
"rules": {}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"files": ["*.json"],
|
|
34
|
+
"parser": "jsonc-eslint-parser",
|
|
35
|
+
"rules": {
|
|
36
|
+
"@nx/dependency-checks": "off"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @ethlete/query
|
|
2
2
|
|
|
3
|
+
## 5.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`3c0cb69`](https://github.com/ethlete-io/ethdk/commit/3c0cb69d3942c1e562905b940b2d75fcb81e77f9) Thanks [@TomTomB](https://github.com/TomTomB)! - Add option to `QueryForm` `resetAllFieldsToDefault` method to skip specific fields by their key
|
|
8
|
+
|
|
9
|
+
- [`3c0cb69`](https://github.com/ethlete-io/ethdk/commit/3c0cb69d3942c1e562905b940b2d75fcb81e77f9) Thanks [@TomTomB](https://github.com/TomTomB)! - Add `appendDefaultValueToUrl` option to `QueryField`
|
|
10
|
+
|
|
11
|
+
## 5.15.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [`69ee325`](https://github.com/ethlete-io/ethdk/commit/69ee32561bf0df78569a1649053a37edf9741b9c) Thanks [@TomTomB](https://github.com/TomTomB)! - Bump only for updating peer deps
|
|
16
|
+
|
|
3
17
|
## 5.15.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
export default {
|
|
3
|
+
displayName: 'query',
|
|
4
|
+
preset: '../../jest.preset.js',
|
|
5
|
+
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
|
6
|
+
globals: {},
|
|
7
|
+
coverageDirectory: '../../coverage/libs/query',
|
|
8
|
+
transform: {
|
|
9
|
+
'^.+\\.(ts|mjs|js|html)$': [
|
|
10
|
+
'jest-preset-angular',
|
|
11
|
+
{
|
|
12
|
+
tsconfig: '<rootDir>/tsconfig.spec.json',
|
|
13
|
+
stringifyContentPathRegex: '\\.(html|svg)$',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
|
18
|
+
snapshotSerializers: [
|
|
19
|
+
'jest-preset-angular/build/serializers/no-ng-attributes',
|
|
20
|
+
'jest-preset-angular/build/serializers/ng-snapshot',
|
|
21
|
+
'jest-preset-angular/build/serializers/html-comment',
|
|
22
|
+
],
|
|
23
|
+
};
|
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ethlete/query",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.16.0",
|
|
4
4
|
"peerDependencies": {
|
|
5
|
-
"@angular/common": "18.0.
|
|
6
|
-
"@angular/core": "18.0.
|
|
7
|
-
"@angular/forms": "18.0.
|
|
8
|
-
"@angular/router": "18.0.
|
|
5
|
+
"@angular/common": "18.0.6",
|
|
6
|
+
"@angular/core": "18.0.6",
|
|
7
|
+
"@angular/forms": "18.0.6",
|
|
8
|
+
"@angular/router": "18.0.6",
|
|
9
9
|
"@ethlete/core": "^4.0.3",
|
|
10
10
|
"@ethlete/types": "^1.6.2",
|
|
11
11
|
"rxjs": "^7.8.1"
|
|
12
|
-
},
|
|
13
|
-
"module": "fesm2022/ethlete-query.mjs",
|
|
14
|
-
"typings": "index.d.ts",
|
|
15
|
-
"exports": {
|
|
16
|
-
"./package.json": {
|
|
17
|
-
"default": "./package.json"
|
|
18
|
-
},
|
|
19
|
-
".": {
|
|
20
|
-
"types": "./index.d.ts",
|
|
21
|
-
"esm2022": "./esm2022/ethlete-query.mjs",
|
|
22
|
-
"esm": "./esm2022/ethlete-query.mjs",
|
|
23
|
-
"default": "./fesm2022/ethlete-query.mjs"
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"sideEffects": false,
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"tslib": "^2.3.0"
|
|
29
12
|
}
|
|
30
|
-
}
|
|
13
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "query",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "libs/query/src",
|
|
5
|
+
"prefix": "et",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"projectType": "library",
|
|
8
|
+
"targets": {
|
|
9
|
+
"build": {
|
|
10
|
+
"executor": "@nx/angular:package",
|
|
11
|
+
"outputs": ["{workspaceRoot}/dist/{projectRoot}"],
|
|
12
|
+
"options": {
|
|
13
|
+
"project": "libs/query/ng-package.json"
|
|
14
|
+
},
|
|
15
|
+
"configurations": {
|
|
16
|
+
"production": {
|
|
17
|
+
"tsConfig": "libs/query/tsconfig.lib.prod.json"
|
|
18
|
+
},
|
|
19
|
+
"development": {
|
|
20
|
+
"tsConfig": "libs/query/tsconfig.lib.json"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"defaultConfiguration": "production"
|
|
24
|
+
},
|
|
25
|
+
"test": {
|
|
26
|
+
"executor": "@nx/jest:jest",
|
|
27
|
+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
|
28
|
+
"options": {
|
|
29
|
+
"jestConfig": "libs/query/jest.config.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"lint": {
|
|
33
|
+
"executor": "@nx/eslint:lint",
|
|
34
|
+
"outputs": ["{options.outputFile}"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { AnyQueryCreator, QueryArgsOf, QueryResponseOf } from '../query-creator';
|
|
2
|
+
|
|
3
|
+
export interface AuthProvider {
|
|
4
|
+
/**
|
|
5
|
+
* The up to date auth header.
|
|
6
|
+
* This will automatically get injected into every query that has the `secure` property set to `true`.
|
|
7
|
+
*/
|
|
8
|
+
header: Record<string, string>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Cleanup function that gets called once the auth provider gets destroyed.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
cleanUp(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AuthProviderBasicConfig {
|
|
18
|
+
/**
|
|
19
|
+
* Basic auth username
|
|
20
|
+
*/
|
|
21
|
+
username: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Basic auth password
|
|
25
|
+
*/
|
|
26
|
+
password: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AuthProviderCustomHeaderConfig {
|
|
30
|
+
/**
|
|
31
|
+
* The custom header name
|
|
32
|
+
* @example `X-Api-Key`
|
|
33
|
+
*/
|
|
34
|
+
name: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The custom header value
|
|
38
|
+
* @example `myApiKey`
|
|
39
|
+
*/
|
|
40
|
+
value: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AuthProviderBearerConfig<T extends AnyQueryCreator> {
|
|
44
|
+
/**
|
|
45
|
+
* The initial jwt
|
|
46
|
+
*/
|
|
47
|
+
token?: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Refresh token configuration
|
|
51
|
+
*/
|
|
52
|
+
refreshConfig?: BearerRefreshConfig<T>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface BearerRefreshConfig<T extends AnyQueryCreator> {
|
|
56
|
+
/**
|
|
57
|
+
* The query used to trade the refresh token for a new token response.
|
|
58
|
+
*/
|
|
59
|
+
queryCreator: T;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Determines if the token should be refreshed if **any** query returns a 401 response.
|
|
63
|
+
* @default true
|
|
64
|
+
*/
|
|
65
|
+
refreshOnUnauthorizedResponse?: boolean;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The initial refresh token
|
|
69
|
+
*/
|
|
70
|
+
token?: string;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The cookie name where the refresh token is stored
|
|
74
|
+
*/
|
|
75
|
+
cookieName?: string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The domain of the cookie. If not set, the current origin will be used.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* "https://example.com" -> "example.com"
|
|
82
|
+
* "https://sub.example.com" -> "example.com"
|
|
83
|
+
*/
|
|
84
|
+
cookieDomain?: string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The days until the cookie expires
|
|
88
|
+
* @default 30
|
|
89
|
+
*/
|
|
90
|
+
cookieExpiresInDays?: number;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The path of the cookie
|
|
94
|
+
* @default '/'
|
|
95
|
+
*/
|
|
96
|
+
cookiePath?: string;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Enable or disable the cookie
|
|
100
|
+
* @default true
|
|
101
|
+
*/
|
|
102
|
+
cookieEnabled?: boolean;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The same site property of the cookie
|
|
106
|
+
* @default 'lax'
|
|
107
|
+
*/
|
|
108
|
+
cookieSameSite?: 'strict' | 'none' | 'lax';
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The time in milliseconds before the token expires when the refresh should be triggered.
|
|
112
|
+
* @default 300000 (5 minutes)
|
|
113
|
+
*/
|
|
114
|
+
refreshBuffer?: number;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The expires in property name inside the jwt body
|
|
118
|
+
* @default 'exp'
|
|
119
|
+
*/
|
|
120
|
+
expiresInPropertyName?: string;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Determines when the token should be refreshed.
|
|
124
|
+
* @default AuthBearerRefreshStrategy.BeforeExpiration
|
|
125
|
+
*/
|
|
126
|
+
strategy?: AuthBearerRefreshStrategy;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Adapter function used to build the request body for the refresh request.
|
|
130
|
+
* @default { body: { refreshToken: "refreshToken" } }
|
|
131
|
+
*/
|
|
132
|
+
requestArgsAdapter?: (tokens: { token: string | null; refreshToken: string }) => QueryArgsOf<T>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Adapter function used to extract the token and refreshToken from the response.
|
|
136
|
+
* @default { token: "token", refreshToken: "refreshToken" }
|
|
137
|
+
*/
|
|
138
|
+
responseAdapter?: (response: NonNullable<QueryResponseOf<T>>) => TokenResponse;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const enum AuthBearerRefreshStrategy {
|
|
142
|
+
/**
|
|
143
|
+
* Automatically refresh the token 5 minutes before expiration.
|
|
144
|
+
* The time can be configured with the `refreshBuffer` property.
|
|
145
|
+
*/
|
|
146
|
+
BeforeExpiration = 'beforeExpiration',
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface TokenResponse {
|
|
150
|
+
/**
|
|
151
|
+
* The access token used inside the authorization http header.
|
|
152
|
+
*/
|
|
153
|
+
token: string | null;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The refresh token used for requesting a new token response once the current tokes is expired.
|
|
157
|
+
* This property is required if the bearer refresh config is set.
|
|
158
|
+
*/
|
|
159
|
+
refreshToken: string | null;
|
|
160
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AnyQueryCreator } from '../query-creator';
|
|
2
|
+
import { AuthProvider } from './auth-provider.types';
|
|
3
|
+
import { BasicAuthProvider } from './basic-auth-provider';
|
|
4
|
+
import { BearerAuthProvider } from './bearer-auth-provider';
|
|
5
|
+
import { CustomHeaderAuthProvider } from './custom-header-auth-provider';
|
|
6
|
+
|
|
7
|
+
export const isBasicAuthProvider = (authProvider: AuthProvider): authProvider is BasicAuthProvider =>
|
|
8
|
+
authProvider instanceof BasicAuthProvider;
|
|
9
|
+
|
|
10
|
+
export const isBearerAuthProvider = <T extends AnyQueryCreator>(
|
|
11
|
+
authProvider: AuthProvider,
|
|
12
|
+
): authProvider is BearerAuthProvider<T> => authProvider instanceof BearerAuthProvider;
|
|
13
|
+
|
|
14
|
+
export const isCustomHeaderAuthProvider = (authProvider: AuthProvider): authProvider is CustomHeaderAuthProvider =>
|
|
15
|
+
authProvider instanceof CustomHeaderAuthProvider;
|
|
16
|
+
|
|
17
|
+
export const decryptBearer = <Result = Record<string, unknown>>(token: string) => {
|
|
18
|
+
try {
|
|
19
|
+
const base64Url = token.split('.')[1];
|
|
20
|
+
|
|
21
|
+
if (!base64Url) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
26
|
+
const jsonPayload = decodeURIComponent(
|
|
27
|
+
atob(base64)
|
|
28
|
+
.split('')
|
|
29
|
+
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
30
|
+
.join(''),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return JSON.parse(jsonPayload) as Result;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error(`Invalid bearer token: ${token}`, error);
|
|
36
|
+
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AuthProvider, AuthProviderBasicConfig } from './auth-provider.types';
|
|
2
|
+
|
|
3
|
+
export class BasicAuthProvider implements AuthProvider {
|
|
4
|
+
get header() {
|
|
5
|
+
return { Authorization: `Basic ${this._basicAuthString}` };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
private get _basicAuthString() {
|
|
9
|
+
return btoa(`${this._config.username}:${this._config.password}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
constructor(public _config: AuthProviderBasicConfig) {}
|
|
13
|
+
|
|
14
|
+
cleanUp(): void {
|
|
15
|
+
// noop
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { deleteCookie, getCookie, setCookie } from '@ethlete/core';
|
|
2
|
+
import { BehaviorSubject, Subject, takeUntil, tap, timer } from 'rxjs';
|
|
3
|
+
import {
|
|
4
|
+
isQueryStateFailure,
|
|
5
|
+
isQueryStateLoading,
|
|
6
|
+
isQueryStateSuccess,
|
|
7
|
+
switchQueryState,
|
|
8
|
+
takeUntilResponse,
|
|
9
|
+
} from '../query';
|
|
10
|
+
import { AnyQueryCreator, ConstructQuery, QueryResponseOf } from '../query-creator';
|
|
11
|
+
import {
|
|
12
|
+
AuthBearerRefreshStrategy,
|
|
13
|
+
AuthProvider,
|
|
14
|
+
AuthProviderBearerConfig,
|
|
15
|
+
TokenResponse,
|
|
16
|
+
} from './auth-provider.types';
|
|
17
|
+
import { decryptBearer } from './auth-provider.utils';
|
|
18
|
+
|
|
19
|
+
export class BearerAuthProvider<T extends AnyQueryCreator> implements AuthProvider {
|
|
20
|
+
private readonly _destroy$ = new Subject<boolean>();
|
|
21
|
+
private readonly _currentRefreshQuery$ = new BehaviorSubject<ConstructQuery<T> | null>(null);
|
|
22
|
+
|
|
23
|
+
private readonly _tokens$ = new BehaviorSubject<TokenResponse>({
|
|
24
|
+
token: null,
|
|
25
|
+
refreshToken: null,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
get header() {
|
|
29
|
+
return { Authorization: `Bearer ${this._tokens$.getValue().token}` };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get tokens$() {
|
|
33
|
+
return this._tokens$.asObservable();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get tokens() {
|
|
37
|
+
return this._tokens$.getValue();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get currentRefreshQuery$() {
|
|
41
|
+
return this._currentRefreshQuery$.asObservable();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get currentRefreshQuery() {
|
|
45
|
+
return this._currentRefreshQuery$.getValue();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get shouldRefreshOnUnauthorizedResponse() {
|
|
49
|
+
return (
|
|
50
|
+
!!this._config.refreshConfig &&
|
|
51
|
+
(this._config.refreshConfig.refreshOnUnauthorizedResponse === undefined ||
|
|
52
|
+
this._config.refreshConfig.refreshOnUnauthorizedResponse)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
constructor(public _config: AuthProviderBearerConfig<T>) {
|
|
57
|
+
const cookieEnabled = _config.refreshConfig?.cookieEnabled ?? true;
|
|
58
|
+
const cookieToken =
|
|
59
|
+
_config.refreshConfig?.cookieName && cookieEnabled ? getCookie(_config.refreshConfig.cookieName) ?? null : null;
|
|
60
|
+
|
|
61
|
+
this._tokens$.next({
|
|
62
|
+
token: _config.token || null,
|
|
63
|
+
refreshToken: _config.refreshConfig?.token || cookieToken || null,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!this.tokens.token && !this.tokens.refreshToken) {
|
|
67
|
+
if (!_config.refreshConfig?.cookieName) {
|
|
68
|
+
console.error(
|
|
69
|
+
'A BearerAuthProvider was created without token or refresh token. You should provide at least a cookieName where the refresh token might be stored.',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (this.tokens.token) {
|
|
77
|
+
this._prepareForRefresh();
|
|
78
|
+
|
|
79
|
+
if (this.tokens.refreshToken && this._config.refreshConfig?.cookieName && cookieEnabled) {
|
|
80
|
+
this._setCookie();
|
|
81
|
+
}
|
|
82
|
+
} else if (this.tokens.refreshToken) {
|
|
83
|
+
this._refreshQuery();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
cleanUp(): void {
|
|
88
|
+
this._destroy$.next(true);
|
|
89
|
+
this._destroy$.unsubscribe();
|
|
90
|
+
this._currentRefreshQuery$.next(null);
|
|
91
|
+
|
|
92
|
+
if (this._config.refreshConfig?.cookieName) {
|
|
93
|
+
this._deleteCookie();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
enableCookie() {
|
|
98
|
+
if (!this._config.refreshConfig?.cookieName) {
|
|
99
|
+
throw new Error('No cookie name was provided');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!this.tokens.refreshToken) {
|
|
103
|
+
throw new Error('No refresh token was provided');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this._config.refreshConfig.cookieEnabled = true;
|
|
107
|
+
|
|
108
|
+
this._setCookie();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
disableCookie() {
|
|
112
|
+
if (!this._config.refreshConfig?.cookieName) {
|
|
113
|
+
throw new Error('No cookie name was provided');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this._config.refreshConfig.cookieEnabled = false;
|
|
117
|
+
|
|
118
|
+
this._deleteCookie();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private _setCookie() {
|
|
122
|
+
if (!this._config.refreshConfig?.cookieName || !this.tokens.refreshToken) return;
|
|
123
|
+
|
|
124
|
+
setCookie(
|
|
125
|
+
this._config.refreshConfig.cookieName,
|
|
126
|
+
this.tokens.refreshToken,
|
|
127
|
+
this._config.refreshConfig.cookieExpiresInDays,
|
|
128
|
+
this._config.refreshConfig.cookieDomain,
|
|
129
|
+
this._config.refreshConfig.cookiePath,
|
|
130
|
+
this._config.refreshConfig.cookieSameSite,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private _deleteCookie() {
|
|
135
|
+
if (!this._config.refreshConfig?.cookieName) return;
|
|
136
|
+
|
|
137
|
+
deleteCookie(
|
|
138
|
+
this._config.refreshConfig.cookieName,
|
|
139
|
+
this._config.refreshConfig.cookiePath,
|
|
140
|
+
this._config.refreshConfig.cookieDomain,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private _prepareForRefresh() {
|
|
145
|
+
if (!this.tokens.token || !this._config.refreshConfig) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const bearer = decryptBearer(this.tokens.token);
|
|
150
|
+
|
|
151
|
+
if (!bearer) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const expiresInPropertyName = this._config.refreshConfig.expiresInPropertyName || 'exp';
|
|
156
|
+
const expiresIn = bearer[expiresInPropertyName] as number | undefined;
|
|
157
|
+
const fiveMinutes = 5 * 60 * 1000;
|
|
158
|
+
const refreshBuffer = this._config.refreshConfig.refreshBuffer ?? fiveMinutes;
|
|
159
|
+
|
|
160
|
+
if (expiresIn === undefined) {
|
|
161
|
+
throw new Error(`Bearer token does not contain an '${expiresInPropertyName}' property`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const remainingTime = new Date(expiresIn * 1000).getTime() - refreshBuffer - new Date().getTime();
|
|
165
|
+
|
|
166
|
+
const strategy = this._config.refreshConfig.strategy ?? AuthBearerRefreshStrategy.BeforeExpiration;
|
|
167
|
+
|
|
168
|
+
switch (strategy) {
|
|
169
|
+
case AuthBearerRefreshStrategy.BeforeExpiration:
|
|
170
|
+
timer(remainingTime)
|
|
171
|
+
.pipe(takeUntil(this._destroy$))
|
|
172
|
+
.subscribe(() => this._refreshQuery());
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @internal
|
|
179
|
+
*/
|
|
180
|
+
_refreshQuery() {
|
|
181
|
+
const currentQuery = this._currentRefreshQuery$.getValue();
|
|
182
|
+
|
|
183
|
+
if (isQueryStateLoading(currentQuery?.rawState)) {
|
|
184
|
+
return currentQuery;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const currentRefreshToken = this.tokens.refreshToken;
|
|
188
|
+
|
|
189
|
+
if (!currentRefreshToken || !this._config.refreshConfig) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const args = this._config.refreshConfig.requestArgsAdapter?.({
|
|
194
|
+
token: this.tokens.token,
|
|
195
|
+
refreshToken: currentRefreshToken,
|
|
196
|
+
}) ?? {
|
|
197
|
+
body: { refreshToken: currentRefreshToken },
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const query = this._config.refreshConfig.queryCreator.prepare(args).execute({ skipCache: true });
|
|
201
|
+
|
|
202
|
+
this._currentRefreshQuery$.next(query as ConstructQuery<T>);
|
|
203
|
+
|
|
204
|
+
this._currentRefreshQuery$
|
|
205
|
+
.pipe(
|
|
206
|
+
switchQueryState(),
|
|
207
|
+
tap((state) => {
|
|
208
|
+
if (isQueryStateSuccess(state)) {
|
|
209
|
+
if (this._config.refreshConfig?.responseAdapter) {
|
|
210
|
+
const tokens = this._config.refreshConfig.responseAdapter(state.response as QueryResponseOf<T>);
|
|
211
|
+
|
|
212
|
+
this._tokens$.next(tokens);
|
|
213
|
+
} else {
|
|
214
|
+
this._tokens$.next({
|
|
215
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
216
|
+
token: (state.response as any)['token'],
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
218
|
+
refreshToken: (state.response as any)['refreshToken'],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const cookieEnabled = this._config.refreshConfig?.cookieEnabled ?? true;
|
|
222
|
+
|
|
223
|
+
if (this._config.refreshConfig?.cookieName && this.tokens.refreshToken && cookieEnabled) {
|
|
224
|
+
this._setCookie();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this._prepareForRefresh();
|
|
228
|
+
} else if (isQueryStateFailure(state)) {
|
|
229
|
+
this._tokens$.next({ token: null, refreshToken: null });
|
|
230
|
+
|
|
231
|
+
if (this._config.refreshConfig?.cookieName) {
|
|
232
|
+
this._deleteCookie();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}),
|
|
236
|
+
takeUntilResponse(),
|
|
237
|
+
takeUntil(this._destroy$),
|
|
238
|
+
)
|
|
239
|
+
.subscribe();
|
|
240
|
+
|
|
241
|
+
return query;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AuthProvider, AuthProviderCustomHeaderConfig } from './auth-provider.types';
|
|
2
|
+
|
|
3
|
+
export class CustomHeaderAuthProvider implements AuthProvider {
|
|
4
|
+
get header() {
|
|
5
|
+
return { [this._config.name]: this._config.value };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
constructor(public _config: AuthProviderCustomHeaderConfig) {}
|
|
9
|
+
|
|
10
|
+
cleanUp(): void {
|
|
11
|
+
// noop
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ClassValidatorError } from './class-validator.types';
|
|
2
|
+
|
|
3
|
+
export const isClassValidatorError = (error: unknown): error is ClassValidatorError => {
|
|
4
|
+
return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error && 'error' in error;
|
|
5
|
+
};
|