@friggframework/core 2.0.0-next.8 → 2.0.0-next.81
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/CLAUDE.md +702 -0
- package/README.md +959 -50
- package/application/commands/README.md +451 -0
- package/application/commands/credential-commands.js +245 -0
- package/application/commands/entity-commands.js +336 -0
- package/application/commands/integration-commands.js +210 -0
- package/application/commands/scheduler-commands.js +263 -0
- package/application/commands/user-commands.js +283 -0
- package/application/index.js +73 -0
- package/assertions/index.js +0 -3
- package/core/CLAUDE.md +690 -0
- package/core/Worker.js +60 -24
- package/core/create-handler.js +79 -8
- package/credential/repositories/credential-repository-documentdb.js +304 -0
- package/credential/repositories/credential-repository-factory.js +54 -0
- package/credential/repositories/credential-repository-interface.js +98 -0
- package/credential/repositories/credential-repository-mongo.js +269 -0
- package/credential/repositories/credential-repository-postgres.js +287 -0
- package/credential/repositories/credential-repository.js +300 -0
- package/credential/use-cases/get-credential-for-user.js +25 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +154 -0
- package/database/documentdb-encryption-service.js +330 -0
- package/database/documentdb-utils.js +136 -0
- package/database/encryption/README.md +839 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +268 -0
- package/database/encryption/field-encryption-service.js +226 -0
- package/database/encryption/logger.js +79 -0
- package/database/encryption/prisma-encryption-extension.js +222 -0
- package/database/index.js +21 -21
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +138 -0
- package/database/repositories/health-check-repository-factory.js +48 -0
- package/database/repositories/health-check-repository-interface.js +82 -0
- package/database/repositories/health-check-repository-mongodb.js +89 -0
- package/database/repositories/health-check-repository-postgres.js +82 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-health-use-case.js +29 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +83 -0
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/run-database-migration-use-case.js +139 -0
- package/database/use-cases/test-encryption-use-case.js +253 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +94 -0
- package/database/utils/mongodb-schema-init.js +108 -0
- package/database/utils/prisma-runner.js +477 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
- package/encrypt/Cryptor.js +34 -168
- package/encrypt/index.js +1 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +15 -7
- package/errors/index.js +2 -0
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +4 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +4 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +335 -0
- package/generated/prisma-mongodb/index-browser.js +317 -0
- package/generated/prisma-mongodb/index.d.ts +22955 -0
- package/generated/prisma-mongodb/index.js +360 -0
- package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
- package/generated/prisma-mongodb/runtime/edge.js +34 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
- package/generated/prisma-mongodb/runtime/library.js +146 -0
- package/generated/prisma-mongodb/runtime/react-native.js +83 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-mongodb/schema.prisma +368 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +342 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +4 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +4 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +357 -0
- package/generated/prisma-postgresql/index-browser.js +339 -0
- package/generated/prisma-postgresql/index.d.ts +25135 -0
- package/generated/prisma-postgresql/index.js +382 -0
- package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
- package/generated/prisma-postgresql/runtime/edge.js +34 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
- package/generated/prisma-postgresql/runtime/library.js +146 -0
- package/generated/prisma-postgresql/runtime/react-native.js +83 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
- package/generated/prisma-postgresql/schema.prisma +351 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +364 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/app-definition-loader.js +38 -0
- package/handlers/app-handler-helpers.js +57 -0
- package/handlers/backend-utils.js +262 -0
- package/handlers/database-migration-handler.js +227 -0
- package/handlers/integration-event-dispatcher.js +54 -0
- package/handlers/routers/HEALTHCHECK.md +342 -0
- package/handlers/routers/auth.js +15 -0
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +326 -0
- package/handlers/routers/health.js +516 -0
- package/handlers/routers/integration-defined-routers.js +45 -0
- package/handlers/routers/integration-webhook-routers.js +67 -0
- package/handlers/routers/user.js +63 -0
- package/handlers/routers/websocket.js +57 -0
- package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
- package/handlers/workers/db-migration.js +352 -0
- package/handlers/workers/dlq-processor.js +63 -0
- package/handlers/workers/integration-defined-workers.js +23 -0
- package/index.js +82 -46
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
- package/infrastructure/scheduler/index.js +33 -0
- package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
- package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/index.js +12 -10
- package/integrations/integration-base.js +364 -55
- package/integrations/integration-router.js +376 -179
- package/integrations/options.js +1 -1
- package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
- package/integrations/repositories/integration-mapping-repository.js +156 -0
- package/integrations/repositories/integration-repository-documentdb.js +219 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +144 -0
- package/integrations/repositories/integration-repository-mongo.js +330 -0
- package/integrations/repositories/integration-repository-postgres.js +385 -0
- package/integrations/repositories/process-repository-documentdb.js +311 -0
- package/integrations/repositories/process-repository-factory.js +53 -0
- package/integrations/repositories/process-repository-interface.js +136 -0
- package/integrations/repositories/process-repository-mongo.js +262 -0
- package/integrations/repositories/process-repository-postgres.js +380 -0
- package/integrations/repositories/process-update-ops-shared.js +112 -0
- package/integrations/tests/doubles/config-capturing-integration.js +81 -0
- package/integrations/tests/doubles/dummy-integration-class.js +105 -0
- package/integrations/tests/doubles/test-integration-repository.js +112 -0
- package/integrations/use-cases/create-integration.js +83 -0
- package/integrations/use-cases/create-process.js +128 -0
- package/integrations/use-cases/delete-integration-for-user.js +101 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +83 -0
- package/integrations/use-cases/get-integrations-for-user.js +88 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/get-process.js +87 -0
- package/integrations/use-cases/index.js +19 -0
- package/integrations/use-cases/load-integration-context.js +71 -0
- package/integrations/use-cases/update-integration-messages.js +44 -0
- package/integrations/use-cases/update-integration-status.js +32 -0
- package/integrations/use-cases/update-integration.js +92 -0
- package/integrations/use-cases/update-process-metrics.js +205 -0
- package/integrations/use-cases/update-process-state.js +158 -0
- package/integrations/utils/map-integration-dto.js +37 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/logs/logger.js +0 -4
- package/{module-plugin → modules}/index.js +0 -10
- package/modules/module-factory.js +56 -0
- package/modules/module.js +258 -0
- package/modules/repositories/module-repository-documentdb.js +335 -0
- package/modules/repositories/module-repository-factory.js +40 -0
- package/modules/repositories/module-repository-interface.js +129 -0
- package/modules/repositories/module-repository-mongo.js +408 -0
- package/modules/repositories/module-repository-postgres.js +453 -0
- package/modules/repositories/module-repository.js +345 -0
- package/modules/requester/api-key.js +52 -0
- package/modules/requester/oauth-2.js +396 -0
- package/modules/requester/requester.js +275 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +39 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +71 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +34 -0
- package/modules/use-cases/get-module.js +74 -0
- package/modules/use-cases/process-authorization-callback.js +177 -0
- package/modules/use-cases/refresh-entity-options.js +72 -0
- package/modules/use-cases/test-module-auth.js +72 -0
- package/modules/utils/map-module-dto.js +18 -0
- package/package.json +82 -50
- package/prisma-mongodb/schema.prisma +368 -0
- package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
- package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
- package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
- package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
- package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
- package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +351 -0
- package/queues/queuer-util.js +103 -21
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +43 -0
- package/syncs/repositories/sync-repository-interface.js +109 -0
- package/syncs/repositories/sync-repository-mongo.js +239 -0
- package/syncs/repositories/sync-repository-postgres.js +319 -0
- package/syncs/sync.js +0 -1
- package/token/repositories/token-repository-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +40 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +219 -0
- package/token/repositories/token-repository-postgres.js +264 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/associations/index.d.ts +0 -17
- package/types/core/index.d.ts +12 -4
- package/types/database/index.d.ts +10 -2
- package/types/encrypt/index.d.ts +5 -3
- package/types/integrations/index.d.ts +3 -8
- package/types/module-plugin/index.d.ts +17 -69
- package/types/syncs/index.d.ts +0 -17
- package/user/repositories/user-repository-documentdb.js +441 -0
- package/user/repositories/user-repository-factory.js +52 -0
- package/user/repositories/user-repository-interface.js +201 -0
- package/user/repositories/user-repository-mongo.js +308 -0
- package/user/repositories/user-repository-postgres.js +360 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/use-cases/authenticate-user.js +127 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-bearer-token.js +77 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +125 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
- package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
- package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
- package/websocket/repositories/websocket-connection-repository.js +161 -0
- package/assertions/is-equal.js +0 -17
- package/associations/model.js +0 -54
- package/database/models/IndividualUser.js +0 -76
- package/database/models/OrganizationUser.js +0 -29
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/models/UserModel.js +0 -7
- package/database/models/WebsocketConnection.js +0 -49
- package/database/mongo.js +0 -45
- package/database/mongoose.js +0 -5
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -132
- package/encrypt/encrypt.test.js +0 -1069
- package/encrypt/test-encrypt.js +0 -107
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-mapping.js +0 -43
- package/integrations/integration-model.js +0 -46
- package/integrations/integration-user.js +0 -144
- package/integrations/test/integration-base.test.js +0 -144
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/module-plugin/auther.js +0 -393
- package/module-plugin/credential.js +0 -22
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/entity.js +0 -46
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/requester/api-key.js +0 -36
- package/module-plugin/requester/oauth-2.js +0 -219
- package/module-plugin/requester/requester.js +0 -165
- package/module-plugin/requester/requester.test.js +0 -28
- package/module-plugin/test/auther.test.js +0 -97
- package/syncs/model.js +0 -62
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
const { Requester } = require('./requester');
|
|
2
|
+
const { get } = require('../../assertions');
|
|
3
|
+
const { ModuleConstants } = require('../ModuleConstants');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OAuth 2.0 Requester - Base class for API modules using OAuth 2.0 authentication.
|
|
7
|
+
*
|
|
8
|
+
* Supports multiple OAuth 2.0 grant types:
|
|
9
|
+
* - `authorization_code` (default): Standard OAuth flow with user consent
|
|
10
|
+
* - `client_credentials`: Server-to-server authentication without user
|
|
11
|
+
* - `password`: Resource Owner Password Credentials grant
|
|
12
|
+
*
|
|
13
|
+
* @extends Requester
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* // Authorization Code flow (default)
|
|
17
|
+
* const api = new MyApi({ grant_type: 'authorization_code' });
|
|
18
|
+
* const authUrl = api.getAuthorizationUri();
|
|
19
|
+
* // After user authorizes...
|
|
20
|
+
* await api.getTokenFromCode(code);
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Client Credentials flow
|
|
24
|
+
* const api = new MyApi({
|
|
25
|
+
* grant_type: 'client_credentials',
|
|
26
|
+
* client_id: process.env.CLIENT_ID,
|
|
27
|
+
* client_secret: process.env.CLIENT_SECRET,
|
|
28
|
+
* audience: 'https://api.example.com',
|
|
29
|
+
* });
|
|
30
|
+
* await api.getTokenFromClientCredentials();
|
|
31
|
+
*/
|
|
32
|
+
class OAuth2Requester extends Requester {
|
|
33
|
+
static requesterType = ModuleConstants.authType.oauth2;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates an OAuth2Requester instance.
|
|
37
|
+
*
|
|
38
|
+
* @param {Object} params - Configuration parameters
|
|
39
|
+
* @param {string} [params.grant_type='authorization_code'] - OAuth grant type:
|
|
40
|
+
* 'authorization_code', 'client_credentials', or 'password'
|
|
41
|
+
* @param {string} [params.client_id] - OAuth client ID
|
|
42
|
+
* @param {string} [params.client_secret] - OAuth client secret
|
|
43
|
+
* @param {string} [params.redirect_uri] - OAuth redirect URI for authorization code flow
|
|
44
|
+
* @param {string} [params.scope] - OAuth scopes (space-separated)
|
|
45
|
+
* @param {string} [params.authorizationUri] - Authorization endpoint URL
|
|
46
|
+
* @param {string} [params.tokenUri] - Token endpoint URL for exchanging codes/credentials
|
|
47
|
+
* @param {string} [params.baseURL] - Base URL for API requests
|
|
48
|
+
* @param {string} [params.access_token] - Existing access token
|
|
49
|
+
* @param {string} [params.refresh_token] - Existing refresh token
|
|
50
|
+
* @param {Date} [params.accessTokenExpire] - Access token expiration date
|
|
51
|
+
* @param {Date} [params.refreshTokenExpire] - Refresh token expiration date
|
|
52
|
+
* @param {string} [params.audience] - Token audience (for client_credentials)
|
|
53
|
+
* @param {string} [params.username] - Username (for password grant)
|
|
54
|
+
* @param {string} [params.password] - Password (for password grant)
|
|
55
|
+
* @param {string} [params.state] - OAuth state parameter for CSRF protection
|
|
56
|
+
*/
|
|
57
|
+
constructor(params) {
|
|
58
|
+
super(params);
|
|
59
|
+
/** @type {string} Delegate type for token update notifications */
|
|
60
|
+
this.DLGT_TOKEN_UPDATE = 'TOKEN_UPDATE';
|
|
61
|
+
/** @type {string} Delegate type for token deauthorization notifications */
|
|
62
|
+
this.DLGT_TOKEN_DEAUTHORIZED = 'TOKEN_DEAUTHORIZED';
|
|
63
|
+
|
|
64
|
+
this.delegateTypes.push(this.DLGT_TOKEN_UPDATE);
|
|
65
|
+
this.delegateTypes.push(this.DLGT_TOKEN_DEAUTHORIZED);
|
|
66
|
+
|
|
67
|
+
/** @type {string} OAuth grant type */
|
|
68
|
+
this.grant_type = get(params, 'grant_type', 'authorization_code');
|
|
69
|
+
/** @type {string|null} OAuth client ID */
|
|
70
|
+
this.client_id = get(params, 'client_id', null);
|
|
71
|
+
/** @type {string|null} OAuth client secret */
|
|
72
|
+
this.client_secret = get(params, 'client_secret', null);
|
|
73
|
+
/** @type {string|null} OAuth redirect URI */
|
|
74
|
+
this.redirect_uri = get(params, 'redirect_uri', null);
|
|
75
|
+
/** @type {string|null} OAuth scopes */
|
|
76
|
+
this.scope = get(params, 'scope', null);
|
|
77
|
+
/** @type {string|null} Authorization endpoint URL */
|
|
78
|
+
this.authorizationUri = get(params, 'authorizationUri', null);
|
|
79
|
+
/** @type {string|null} Token endpoint URL */
|
|
80
|
+
this.tokenUri = get(params, 'tokenUri', null);
|
|
81
|
+
/** @type {string|null} Base URL for API requests */
|
|
82
|
+
this.baseURL = get(params, 'baseURL', null);
|
|
83
|
+
/** @type {string|null} Current access token */
|
|
84
|
+
this.access_token = get(params, 'access_token', null);
|
|
85
|
+
/** @type {string|null} Current refresh token */
|
|
86
|
+
this.refresh_token = get(params, 'refresh_token', null);
|
|
87
|
+
/** @type {Date|null} Access token expiration */
|
|
88
|
+
this.accessTokenExpire = get(params, 'accessTokenExpire', null);
|
|
89
|
+
/** @type {Date|null} Refresh token expiration */
|
|
90
|
+
this.refreshTokenExpire = get(params, 'refreshTokenExpire', null);
|
|
91
|
+
/** @type {string|null} Token audience */
|
|
92
|
+
this.audience = get(params, 'audience', null);
|
|
93
|
+
/** @type {string|null} Username for password grant */
|
|
94
|
+
this.username = get(params, 'username', null);
|
|
95
|
+
/** @type {string|null} Password for password grant */
|
|
96
|
+
this.password = get(params, 'password', null);
|
|
97
|
+
/** @type {string|null} OAuth state for CSRF protection */
|
|
98
|
+
this.state = get(params, 'state', null);
|
|
99
|
+
|
|
100
|
+
/** @type {boolean} Whether this requester supports token refresh */
|
|
101
|
+
this.isRefreshable = true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sets OAuth tokens and calculates expiration times.
|
|
106
|
+
* Notifies delegates of token update via DLGT_TOKEN_UPDATE.
|
|
107
|
+
*
|
|
108
|
+
* @param {Object} params - Token response from OAuth server
|
|
109
|
+
* @param {string} params.access_token - The access token
|
|
110
|
+
* @param {string} [params.refresh_token] - The refresh token (if provided)
|
|
111
|
+
* @param {number} [params.expires_in] - Access token lifetime in seconds
|
|
112
|
+
* @param {number} [params.x_refresh_token_expires_in] - Refresh token lifetime in seconds
|
|
113
|
+
* @returns {Promise<void>}
|
|
114
|
+
*/
|
|
115
|
+
async setTokens(params) {
|
|
116
|
+
this.access_token = get(params, 'access_token');
|
|
117
|
+
const newRefreshToken = get(params, 'refresh_token', null);
|
|
118
|
+
if (newRefreshToken !== null) {
|
|
119
|
+
this.refresh_token = newRefreshToken;
|
|
120
|
+
} else {
|
|
121
|
+
if (this.refresh_token) {
|
|
122
|
+
console.log(
|
|
123
|
+
'[Frigg] No refresh_token in response, preserving existing'
|
|
124
|
+
);
|
|
125
|
+
} else {
|
|
126
|
+
console.log(
|
|
127
|
+
'[Frigg] Current refresh_token is null and no new refresh_token in response'
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const accessExpiresIn = get(params, 'expires_in', null);
|
|
132
|
+
const refreshExpiresIn = get(
|
|
133
|
+
params,
|
|
134
|
+
'x_refresh_token_expires_in',
|
|
135
|
+
null
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
this.accessTokenExpire = new Date(Date.now() + accessExpiresIn * 1000);
|
|
139
|
+
if (refreshExpiresIn !== null) {
|
|
140
|
+
this.refreshTokenExpire = new Date(
|
|
141
|
+
Date.now() + refreshExpiresIn * 1000
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await this.notify(this.DLGT_TOKEN_UPDATE);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Gets the OAuth authorization URL for initiating the authorization code flow.
|
|
150
|
+
*
|
|
151
|
+
* @returns {string|null} The authorization URL
|
|
152
|
+
*/
|
|
153
|
+
getAuthorizationUri() {
|
|
154
|
+
return this.authorizationUri;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Returns authorization requirements for this OAuth flow.
|
|
159
|
+
*
|
|
160
|
+
* @returns {{url: string|null, type: string}} Authorization requirements
|
|
161
|
+
*/
|
|
162
|
+
getAuthorizationRequirements() {
|
|
163
|
+
return {
|
|
164
|
+
url: this.getAuthorizationUri(),
|
|
165
|
+
type: 'oauth2',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Exchanges an authorization code for access and refresh tokens.
|
|
171
|
+
* Requires client_id, client_secret, redirect_uri, and tokenUri to be set.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} code - The authorization code from the OAuth callback
|
|
174
|
+
* @returns {Promise<Object>} Token response containing access_token, refresh_token, etc.
|
|
175
|
+
*/
|
|
176
|
+
async getTokenFromCode(code) {
|
|
177
|
+
const params = new URLSearchParams();
|
|
178
|
+
params.append('grant_type', 'authorization_code');
|
|
179
|
+
params.append('client_id', this.client_id);
|
|
180
|
+
params.append('client_secret', this.client_secret);
|
|
181
|
+
params.append('redirect_uri', this.redirect_uri);
|
|
182
|
+
params.append('scope', this.scope);
|
|
183
|
+
params.append('code', code);
|
|
184
|
+
const options = {
|
|
185
|
+
body: params,
|
|
186
|
+
headers: {
|
|
187
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
188
|
+
},
|
|
189
|
+
url: this.tokenUri,
|
|
190
|
+
};
|
|
191
|
+
const response = await this._post(options, false);
|
|
192
|
+
await this.setTokens(response);
|
|
193
|
+
return response;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Exchanges an authorization code for tokens using Basic Auth header.
|
|
198
|
+
* Alternative to getTokenFromCode() for OAuth servers requiring Basic Auth.
|
|
199
|
+
* Override getTokenFromCode() in child class to use this instead.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} code - The authorization code from the OAuth callback
|
|
202
|
+
* @returns {Promise<Object>} Token response containing access_token, refresh_token, etc.
|
|
203
|
+
*/
|
|
204
|
+
async getTokenFromCodeBasicAuthHeader(code) {
|
|
205
|
+
const params = new URLSearchParams();
|
|
206
|
+
params.append('grant_type', 'authorization_code');
|
|
207
|
+
params.append('client_id', this.client_id);
|
|
208
|
+
params.append('redirect_uri', this.redirect_uri);
|
|
209
|
+
params.append('code', code);
|
|
210
|
+
|
|
211
|
+
const options = {
|
|
212
|
+
body: params,
|
|
213
|
+
headers: {
|
|
214
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
215
|
+
Authorization: `Basic ${Buffer.from(
|
|
216
|
+
`${this.client_id}:${this.client_secret}`
|
|
217
|
+
).toString('base64')}`,
|
|
218
|
+
},
|
|
219
|
+
url: this.tokenUri,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const response = await this._post(options, false);
|
|
223
|
+
await this.setTokens(response);
|
|
224
|
+
return response;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Refreshes the access token using the refresh token.
|
|
229
|
+
* Used for authorization_code and password grant types.
|
|
230
|
+
*
|
|
231
|
+
* @param {Object} refreshTokenObject - Object containing refresh_token
|
|
232
|
+
* @param {string} refreshTokenObject.refresh_token - The refresh token
|
|
233
|
+
* @returns {Promise<Object>} New token response
|
|
234
|
+
*/
|
|
235
|
+
async refreshAccessToken(refreshTokenObject) {
|
|
236
|
+
this.access_token = undefined;
|
|
237
|
+
const params = new URLSearchParams();
|
|
238
|
+
params.append('grant_type', 'refresh_token');
|
|
239
|
+
params.append('client_id', this.client_id);
|
|
240
|
+
params.append('client_secret', this.client_secret);
|
|
241
|
+
params.append('refresh_token', refreshTokenObject.refresh_token);
|
|
242
|
+
params.append('redirect_uri', this.redirect_uri);
|
|
243
|
+
|
|
244
|
+
const options = {
|
|
245
|
+
body: params,
|
|
246
|
+
url: this.tokenUri,
|
|
247
|
+
headers: {
|
|
248
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
console.log('[Frigg] Refreshing access token with options');
|
|
252
|
+
const response = await this._post(options, false);
|
|
253
|
+
await this.setTokens(response);
|
|
254
|
+
return response;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Adds OAuth Bearer token to request headers.
|
|
259
|
+
* Clears any existing Authorization header first to prevent stale tokens
|
|
260
|
+
* from being reused after failed refresh attempts.
|
|
261
|
+
*
|
|
262
|
+
* @param {Object} headers - Headers object to modify
|
|
263
|
+
* @returns {Promise<Object>} Headers with Authorization added
|
|
264
|
+
*/
|
|
265
|
+
async addAuthHeaders(headers) {
|
|
266
|
+
delete headers.Authorization;
|
|
267
|
+
if (this.access_token) {
|
|
268
|
+
headers.Authorization = `Bearer ${this.access_token}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return headers;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Checks if the requester has valid authentication.
|
|
276
|
+
*
|
|
277
|
+
* @returns {boolean} True if authenticated with valid tokens
|
|
278
|
+
*/
|
|
279
|
+
isAuthenticated() {
|
|
280
|
+
return !!(
|
|
281
|
+
this.access_token !== null &&
|
|
282
|
+
this.refresh_token !== null &&
|
|
283
|
+
this.accessTokenExpire &&
|
|
284
|
+
this.refreshTokenExpire
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Refreshes authentication based on the configured grant type.
|
|
290
|
+
* - For authorization_code/password: Uses refreshAccessToken() with refresh_token
|
|
291
|
+
* - For client_credentials: Uses getTokenFromClientCredentials() to get new token
|
|
292
|
+
*
|
|
293
|
+
* On failure, notifies delegates via DLGT_INVALID_AUTH.
|
|
294
|
+
*
|
|
295
|
+
* @returns {Promise<boolean>} True if refresh succeeded, false if failed
|
|
296
|
+
*/
|
|
297
|
+
async refreshAuth() {
|
|
298
|
+
try {
|
|
299
|
+
console.log('[Frigg] Starting token refresh', {
|
|
300
|
+
grant_type: this.grant_type,
|
|
301
|
+
has_refresh_token: !!this.refresh_token,
|
|
302
|
+
has_client_id: !!this.client_id,
|
|
303
|
+
has_client_secret: !!this.client_secret,
|
|
304
|
+
has_token_uri: !!this.tokenUri,
|
|
305
|
+
tokenUri: this.tokenUri,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (this.grant_type !== 'client_credentials') {
|
|
309
|
+
await this.refreshAccessToken({
|
|
310
|
+
refresh_token: this.refresh_token,
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
await this.getTokenFromClientCredentials();
|
|
314
|
+
}
|
|
315
|
+
console.log('[Frigg] Token refresh succeeded');
|
|
316
|
+
return true;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error('[Frigg] Token refresh failed', {
|
|
319
|
+
error_message: error?.message,
|
|
320
|
+
error_name: error?.name,
|
|
321
|
+
response_status: error?.response?.status,
|
|
322
|
+
response_data: error?.response?.data,
|
|
323
|
+
});
|
|
324
|
+
await this.notify(this.DLGT_INVALID_AUTH);
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Obtains tokens using the Resource Owner Password Credentials grant.
|
|
331
|
+
* Requires username and password to be set.
|
|
332
|
+
*
|
|
333
|
+
* @returns {Promise<Object|undefined>} Token response or undefined on error
|
|
334
|
+
*/
|
|
335
|
+
async getTokenFromUsernamePassword() {
|
|
336
|
+
try {
|
|
337
|
+
const url = this.tokenUri;
|
|
338
|
+
|
|
339
|
+
const body = {
|
|
340
|
+
username: this.username,
|
|
341
|
+
password: this.password,
|
|
342
|
+
grant_type: 'password',
|
|
343
|
+
};
|
|
344
|
+
const headers = {
|
|
345
|
+
'Content-Type': 'application/json',
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const tokenRes = await this._post({
|
|
349
|
+
url,
|
|
350
|
+
body,
|
|
351
|
+
headers,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
await this.setTokens(tokenRes);
|
|
355
|
+
return tokenRes;
|
|
356
|
+
} catch {
|
|
357
|
+
await this.notify(this.DLGT_INVALID_AUTH);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Obtains tokens using the Client Credentials grant.
|
|
363
|
+
* Used for server-to-server authentication without a user context.
|
|
364
|
+
* Requires client_id, client_secret, and optionally audience to be set.
|
|
365
|
+
*
|
|
366
|
+
* @returns {Promise<Object|undefined>} Token response or undefined on error
|
|
367
|
+
*/
|
|
368
|
+
async getTokenFromClientCredentials() {
|
|
369
|
+
try {
|
|
370
|
+
const url = this.tokenUri;
|
|
371
|
+
|
|
372
|
+
const body = {
|
|
373
|
+
audience: this.audience,
|
|
374
|
+
client_id: this.client_id,
|
|
375
|
+
client_secret: this.client_secret,
|
|
376
|
+
grant_type: 'client_credentials',
|
|
377
|
+
};
|
|
378
|
+
const headers = {
|
|
379
|
+
'Content-Type': 'application/json',
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const tokenRes = await this._post({
|
|
383
|
+
url,
|
|
384
|
+
body,
|
|
385
|
+
headers,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
await this.setTokens(tokenRes);
|
|
389
|
+
return tokenRes;
|
|
390
|
+
} catch {
|
|
391
|
+
await this.notify(this.DLGT_INVALID_AUTH);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = { OAuth2Requester };
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
const fetch = require('node-fetch');
|
|
2
|
+
const { Delegate } = require('../../core');
|
|
3
|
+
const { FetchError } = require('../../errors');
|
|
4
|
+
const { get } = require('../../assertions');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
7
|
+
|
|
8
|
+
class Requester extends Delegate {
|
|
9
|
+
constructor(params) {
|
|
10
|
+
super(params);
|
|
11
|
+
this.backOff = get(params, 'backOff', [1, 3, 10, 30, 60, 180]);
|
|
12
|
+
this.isRefreshable = false;
|
|
13
|
+
this.refreshCount = 0;
|
|
14
|
+
this.DLGT_INVALID_AUTH = 'INVALID_AUTH';
|
|
15
|
+
this.delegateTypes.push(this.DLGT_INVALID_AUTH);
|
|
16
|
+
this.agent = get(params, 'agent', null);
|
|
17
|
+
|
|
18
|
+
// Per-attempt HTTP timeout. Without this the framework called fetch()
|
|
19
|
+
// with no AbortController and no timeout — a silently-hung TCP
|
|
20
|
+
// connection (server accepts but never responds) blocked the calling
|
|
21
|
+
// promise forever, cascading into stalled batches, stalled syncs,
|
|
22
|
+
// and worker-lambda timeouts.
|
|
23
|
+
//
|
|
24
|
+
// Configuration precedence:
|
|
25
|
+
// 1. Instance param: new Requester({ requestTimeoutMs: 30_000 })
|
|
26
|
+
// 2. Class static: static requestTimeoutMs = 30_000
|
|
27
|
+
// 3. Default: DEFAULT_REQUEST_TIMEOUT_MS (60s)
|
|
28
|
+
//
|
|
29
|
+
// Pass 0 (or null) to disable the timeout entirely — reserved for
|
|
30
|
+
// test doubles and documented long-running endpoints.
|
|
31
|
+
// Intentionally NOT using `get(params, ...)` here — the Frigg
|
|
32
|
+
// `get` helper throws RequiredPropertyError if the key is missing
|
|
33
|
+
// and no default is provided, which would collide with the fall-
|
|
34
|
+
// through to the class-level static override.
|
|
35
|
+
const instanceTimeout = params?.requestTimeoutMs;
|
|
36
|
+
this.requestTimeoutMs =
|
|
37
|
+
instanceTimeout !== undefined && instanceTimeout !== null
|
|
38
|
+
? instanceTimeout
|
|
39
|
+
: this.constructor.requestTimeoutMs ??
|
|
40
|
+
DEFAULT_REQUEST_TIMEOUT_MS;
|
|
41
|
+
|
|
42
|
+
// Allow passing in the fetch function
|
|
43
|
+
// Instance methods can use this.fetch without differentiating
|
|
44
|
+
this.fetch = get(params, 'fetch', fetch);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
parsedBody = async (resp) => {
|
|
48
|
+
const contentType = resp.headers.get('Content-Type') || '';
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
contentType.match(/^application\/json/) ||
|
|
52
|
+
contentType.match(/^application\/vnd.api\+json/) ||
|
|
53
|
+
contentType.match(/^application\/hal\+json/)
|
|
54
|
+
) {
|
|
55
|
+
return resp.json();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return resp.text();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
async _request(url, options, i = 0) {
|
|
62
|
+
let encodedUrl = encodeURI(url);
|
|
63
|
+
if (options.query) {
|
|
64
|
+
let queryBuild = '?';
|
|
65
|
+
for (const key in options.query) {
|
|
66
|
+
queryBuild += `${encodeURIComponent(key)}=${encodeURIComponent(
|
|
67
|
+
options.query[key]
|
|
68
|
+
)}&`;
|
|
69
|
+
}
|
|
70
|
+
encodedUrl += queryBuild.slice(0, -1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
options.headers = await this.addAuthHeaders(options.headers);
|
|
74
|
+
|
|
75
|
+
if (this.agent) options.agent = this.agent;
|
|
76
|
+
|
|
77
|
+
// Per-attempt timeout — fresh AbortController per call so the retry
|
|
78
|
+
// recursion (with its own backoff sleeps) always gets a clean
|
|
79
|
+
// signal. Timer is cleared in the finally block regardless of
|
|
80
|
+
// outcome.
|
|
81
|
+
const timeoutMs = this.requestTimeoutMs;
|
|
82
|
+
const controller = timeoutMs > 0 ? new AbortController() : null;
|
|
83
|
+
const timeoutHandle = controller
|
|
84
|
+
? setTimeout(() => controller.abort(), timeoutMs)
|
|
85
|
+
: null;
|
|
86
|
+
const fetchOptions = controller
|
|
87
|
+
? { ...options, signal: controller.signal }
|
|
88
|
+
: options;
|
|
89
|
+
|
|
90
|
+
// Timer must stay active through body consumption. node-fetch v2
|
|
91
|
+
// resolves the fetch() promise when headers arrive, not when the
|
|
92
|
+
// body is fully read — so a server that sends headers and then
|
|
93
|
+
// stalls the body would still hang parsedBody() or
|
|
94
|
+
// FetchError.create()'s response.text() call. We clear the timer
|
|
95
|
+
// only after the body is fully consumed (success path) or
|
|
96
|
+
// deliberately before each recursive retry so the new attempt
|
|
97
|
+
// starts with its own fresh timer.
|
|
98
|
+
let timerCleared = false;
|
|
99
|
+
const clearRequestTimer = () => {
|
|
100
|
+
if (!timerCleared && timeoutHandle) {
|
|
101
|
+
clearTimeout(timeoutHandle);
|
|
102
|
+
timerCleared = true;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
let response;
|
|
108
|
+
try {
|
|
109
|
+
response = await this.fetch(encodedUrl, fetchOptions);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// AbortController fires AbortError (name) / ETIMEDOUT-shaped
|
|
112
|
+
// errors (type on node-fetch) when we hit the timeout. No
|
|
113
|
+
// retry on timeout: a slow endpoint is a downstream problem,
|
|
114
|
+
// and each retry would wait another `timeoutMs` before giving
|
|
115
|
+
// up — amplifying the hang into a per-record multi-minute
|
|
116
|
+
// stall at batch scale.
|
|
117
|
+
const isTimeout =
|
|
118
|
+
e?.name === 'AbortError' || e?.type === 'aborted';
|
|
119
|
+
if (e?.code === 'ECONNRESET' && i < this.backOff.length) {
|
|
120
|
+
clearRequestTimer();
|
|
121
|
+
const delay = this.backOff[i] * 1000;
|
|
122
|
+
await new Promise((resolve) =>
|
|
123
|
+
setTimeout(resolve, delay)
|
|
124
|
+
);
|
|
125
|
+
return this._request(url, options, i + 1);
|
|
126
|
+
}
|
|
127
|
+
const fetchError = await FetchError.create({
|
|
128
|
+
resource: encodedUrl,
|
|
129
|
+
init: options,
|
|
130
|
+
responseBody: isTimeout
|
|
131
|
+
? `Request timed out after ${timeoutMs}ms`
|
|
132
|
+
: e,
|
|
133
|
+
});
|
|
134
|
+
if (isTimeout) {
|
|
135
|
+
// Flag + machine-readable fields so callers can
|
|
136
|
+
// distinguish a timeout from a generic network error
|
|
137
|
+
// without parsing the message (which FetchError
|
|
138
|
+
// sanitizes outside of STAGE=dev).
|
|
139
|
+
fetchError.isTimeout = true;
|
|
140
|
+
fetchError.timeoutMs = timeoutMs;
|
|
141
|
+
}
|
|
142
|
+
throw fetchError;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const { status } = response;
|
|
146
|
+
|
|
147
|
+
// If the status is retriable and there are back off requests left, retry the request
|
|
148
|
+
if ((status === 429 || status >= 500) && i < this.backOff.length) {
|
|
149
|
+
clearRequestTimer();
|
|
150
|
+
const delay = this.backOff[i] * 1000;
|
|
151
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
152
|
+
return this._request(url, options, i + 1);
|
|
153
|
+
} else if (status === 401) {
|
|
154
|
+
if (!this.isRefreshable || this.refreshCount > 0) {
|
|
155
|
+
await this.notify(this.DLGT_INVALID_AUTH);
|
|
156
|
+
} else {
|
|
157
|
+
this.refreshCount++;
|
|
158
|
+
const refreshSucceeded = await this.refreshAuth();
|
|
159
|
+
if (refreshSucceeded) {
|
|
160
|
+
clearRequestTimer();
|
|
161
|
+
return this._request(url, options, i + 1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// If the error wasn't retried, throw. FetchError.create reads
|
|
167
|
+
// the response body (response.text()) — timer must still be
|
|
168
|
+
// alive to catch a stalled body stream.
|
|
169
|
+
if (status >= 400) {
|
|
170
|
+
const fetchError = await FetchError.create({
|
|
171
|
+
resource: encodedUrl,
|
|
172
|
+
init: options,
|
|
173
|
+
response,
|
|
174
|
+
});
|
|
175
|
+
throw this._maybeFlagTimeoutDuringBodyRead(
|
|
176
|
+
fetchError,
|
|
177
|
+
timeoutMs
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// parsedBody consumes the response body stream. If the server
|
|
182
|
+
// stalls mid-stream the timer (still armed) aborts it.
|
|
183
|
+
return options.returnFullRes
|
|
184
|
+
? response
|
|
185
|
+
: await this.parsedBody(response);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
// If the abort fired during body consumption, node-fetch emits
|
|
188
|
+
// the error as an AbortError on the body stream. Surface the
|
|
189
|
+
// same isTimeout flag callers use for header-phase timeouts.
|
|
190
|
+
throw this._maybeFlagTimeoutDuringBodyRead(e, timeoutMs);
|
|
191
|
+
} finally {
|
|
192
|
+
clearRequestTimer();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
_maybeFlagTimeoutDuringBodyRead(err, timeoutMs) {
|
|
197
|
+
if (!err || typeof err !== 'object') return err;
|
|
198
|
+
if (err.isTimeout) return err;
|
|
199
|
+
const isAbort =
|
|
200
|
+
err.name === 'AbortError' || err.type === 'aborted';
|
|
201
|
+
if (!isAbort) return err;
|
|
202
|
+
err.isTimeout = true;
|
|
203
|
+
err.timeoutMs = timeoutMs;
|
|
204
|
+
return err;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async _get(options) {
|
|
208
|
+
const fetchOptions = {
|
|
209
|
+
method: 'GET',
|
|
210
|
+
credentials: 'include',
|
|
211
|
+
headers: options.headers || {},
|
|
212
|
+
query: options.query || {},
|
|
213
|
+
returnFullRes: options.returnFullRes || false,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const res = await this._request(options.url, fetchOptions);
|
|
217
|
+
return res;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async _post(options, stringify = true) {
|
|
221
|
+
const fetchOptions = {
|
|
222
|
+
method: 'POST',
|
|
223
|
+
credentials: 'include',
|
|
224
|
+
headers: options.headers || {},
|
|
225
|
+
query: options.query || {},
|
|
226
|
+
body: stringify ? JSON.stringify(options.body) : options.body,
|
|
227
|
+
returnFullRes: options.returnFullRes || false,
|
|
228
|
+
};
|
|
229
|
+
const res = await this._request(options.url, fetchOptions);
|
|
230
|
+
return res;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async _patch(options, stringify = true) {
|
|
234
|
+
const fetchOptions = {
|
|
235
|
+
method: 'PATCH',
|
|
236
|
+
credentials: 'include',
|
|
237
|
+
headers: options.headers || {},
|
|
238
|
+
query: options.query || {},
|
|
239
|
+
body: stringify ? JSON.stringify(options.body) : options.body,
|
|
240
|
+
returnFullRes: options.returnFullRes || false,
|
|
241
|
+
};
|
|
242
|
+
const res = await this._request(options.url, fetchOptions);
|
|
243
|
+
return res;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async _put(options, stringify = true) {
|
|
247
|
+
const fetchOptions = {
|
|
248
|
+
method: 'PUT',
|
|
249
|
+
credentials: 'include',
|
|
250
|
+
headers: options.headers || {},
|
|
251
|
+
query: options.query || {},
|
|
252
|
+
body: stringify ? JSON.stringify(options.body) : options.body,
|
|
253
|
+
returnFullRes: options.returnFullRes || false,
|
|
254
|
+
};
|
|
255
|
+
const res = await this._request(options.url, fetchOptions);
|
|
256
|
+
return res;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async _delete(options) {
|
|
260
|
+
const fetchOptions = {
|
|
261
|
+
method: 'DELETE',
|
|
262
|
+
credentials: 'include',
|
|
263
|
+
headers: options.headers || {},
|
|
264
|
+
query: options.query || {},
|
|
265
|
+
returnFullRes: options.returnFullRes || true,
|
|
266
|
+
};
|
|
267
|
+
return this._request(options.url, fetchOptions);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async refreshAuth() {
|
|
271
|
+
throw new Error('refreshAuth not yet defined in child of Requester');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = { Requester };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const { get } = require('
|
|
2
|
-
const { OAuth2Requester } = require('
|
|
1
|
+
const { get } = require('../../../assertions');
|
|
2
|
+
const { OAuth2Requester } = require('../..');
|
|
3
3
|
|
|
4
4
|
class Api extends OAuth2Requester {
|
|
5
5
|
constructor(params) {
|
|
@@ -23,7 +23,12 @@ class Api extends OAuth2Requester {
|
|
|
23
23
|
return this.authorizationUri;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
getAuthorizationRequirements() {
|
|
27
|
+
return {
|
|
28
|
+
url: this.getAuthUri(),
|
|
29
|
+
type: 'oauth2',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
module.exports = { Api };
|