@friggframework/core 2.0.0-next.100 → 2.0.0-next.101
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/integrations/integration-base.js +18 -4
- package/modules/module.js +15 -2
- package/modules/requester/requester.js +62 -13
- package/package.json +5 -5
|
@@ -220,11 +220,11 @@ class IntegrationBase {
|
|
|
220
220
|
/**
|
|
221
221
|
* Returns the modules as object with keys as module names.
|
|
222
222
|
* Uses the keys from Definition.modules to attach modules correctly.
|
|
223
|
-
*
|
|
223
|
+
*
|
|
224
224
|
* Example:
|
|
225
225
|
* Definition.modules = { attio: {...}, quo: { definition: { getName: () => 'quo-attio' } } }
|
|
226
226
|
* Module with getName()='quo-attio' gets attached as this.quo (not this['quo-attio'])
|
|
227
|
-
*
|
|
227
|
+
*
|
|
228
228
|
* @private
|
|
229
229
|
* @param {Array} integrationModules - Array of module instances
|
|
230
230
|
* @returns {Object} The modules object
|
|
@@ -791,8 +791,18 @@ class IntegrationBase {
|
|
|
791
791
|
if (!this.id) return;
|
|
792
792
|
|
|
793
793
|
if (delegateString === 'CREDENTIAL_INVALIDATED') {
|
|
794
|
+
const detail =
|
|
795
|
+
object?.reason || object?.statusCode
|
|
796
|
+
? ` (status ${object?.statusCode ?? '?'}: ${
|
|
797
|
+
object?.reason ?? 'no reason given'
|
|
798
|
+
})`
|
|
799
|
+
: '';
|
|
794
800
|
console.log(
|
|
795
|
-
`[Frigg] Module ${
|
|
801
|
+
`[Frigg] Module ${
|
|
802
|
+
notifier?.name || '?'
|
|
803
|
+
} reported invalid credentials for integration ${
|
|
804
|
+
this.id
|
|
805
|
+
} — marking ERROR${detail}`
|
|
796
806
|
);
|
|
797
807
|
await this.persistStatus('ERROR');
|
|
798
808
|
return;
|
|
@@ -801,7 +811,11 @@ class IntegrationBase {
|
|
|
801
811
|
if (delegateString === 'CREDENTIAL_VALIDATED') {
|
|
802
812
|
if (this.status !== 'ERROR') return;
|
|
803
813
|
console.log(
|
|
804
|
-
`[Frigg] Module ${
|
|
814
|
+
`[Frigg] Module ${
|
|
815
|
+
notifier?.name || '?'
|
|
816
|
+
} reported valid credentials for integration ${
|
|
817
|
+
this.id
|
|
818
|
+
} — clearing ERROR → ENABLED`
|
|
805
819
|
);
|
|
806
820
|
await this.persistStatus('ENABLED');
|
|
807
821
|
}
|
package/modules/module.js
CHANGED
|
@@ -147,15 +147,24 @@ class Module extends Delegate {
|
|
|
147
147
|
} else if (delegateString === this.api.DLGT_TOKEN_DEAUTHORIZED) {
|
|
148
148
|
await this.deauthorize();
|
|
149
149
|
} else if (delegateString === this.api.DLGT_INVALID_AUTH) {
|
|
150
|
-
await this.markCredentialsInvalid();
|
|
150
|
+
await this.markCredentialsInvalid(object);
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
async markCredentialsInvalid() {
|
|
154
|
+
async markCredentialsInvalid(diagnosticInfo = null) {
|
|
155
155
|
if (!this.credential) return;
|
|
156
156
|
|
|
157
157
|
if (!this.credential.id) return;
|
|
158
158
|
|
|
159
|
+
if (diagnosticInfo) {
|
|
160
|
+
console.error(
|
|
161
|
+
`[Frigg] Module ${this.name} credentials rejected (status ${
|
|
162
|
+
diagnosticInfo.statusCode ?? '?'
|
|
163
|
+
}):`,
|
|
164
|
+
diagnosticInfo.message ?? diagnosticInfo
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
159
168
|
await this.credentialRepository.updateAuthenticationStatus(
|
|
160
169
|
this.credential.id,
|
|
161
170
|
false
|
|
@@ -181,6 +190,10 @@ class Module extends Delegate {
|
|
|
181
190
|
await this.notify(this.DLGT_CREDENTIAL_INVALIDATED, {
|
|
182
191
|
credentialId: this.credential.id,
|
|
183
192
|
moduleName: this.name,
|
|
193
|
+
...(diagnosticInfo && {
|
|
194
|
+
reason: diagnosticInfo.message,
|
|
195
|
+
statusCode: diagnosticInfo.statusCode,
|
|
196
|
+
}),
|
|
184
197
|
});
|
|
185
198
|
} catch (err) {
|
|
186
199
|
console.error(
|
|
@@ -4,6 +4,7 @@ const { FetchError } = require('../../errors');
|
|
|
4
4
|
const { get } = require('../../assertions');
|
|
5
5
|
|
|
6
6
|
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
7
|
+
const MAX_AUTH_RETRIES = 3;
|
|
7
8
|
|
|
8
9
|
class Requester extends Delegate {
|
|
9
10
|
constructor(params) {
|
|
@@ -11,6 +12,7 @@ class Requester extends Delegate {
|
|
|
11
12
|
this.backOff = get(params, 'backOff', [1, 3, 10, 30, 60, 180]);
|
|
12
13
|
this.isRefreshable = false;
|
|
13
14
|
this.refreshCount = 0;
|
|
15
|
+
this.authGraceRetryCount = 0;
|
|
14
16
|
this.DLGT_INVALID_AUTH = 'INVALID_AUTH';
|
|
15
17
|
this.delegateTypes.push(this.DLGT_INVALID_AUTH);
|
|
16
18
|
this.agent = get(params, 'agent', null);
|
|
@@ -58,7 +60,16 @@ class Requester extends Delegate {
|
|
|
58
60
|
return resp.text();
|
|
59
61
|
};
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
/**
|
|
64
|
+
* @param {string} url - The request URL, relative or absolute.
|
|
65
|
+
* @param {Object} options - Fetch options (method, headers, body, query,
|
|
66
|
+
* returnFullRes, etc.) built by the `_get`/`_post`/`_patch`/`_put`/
|
|
67
|
+
* `_delete` wrappers.
|
|
68
|
+
* @param {number} attempt - 0-based count of retries already made for
|
|
69
|
+
* this call. Indexes `this.backOff` for the next delay and is passed
|
|
70
|
+
* back in on each recursive retry.
|
|
71
|
+
*/
|
|
72
|
+
async _request(url, options, attempt = 0) {
|
|
62
73
|
let encodedUrl = encodeURI(url);
|
|
63
74
|
if (options.query) {
|
|
64
75
|
let queryBuild = '?';
|
|
@@ -116,11 +127,11 @@ class Requester extends Delegate {
|
|
|
116
127
|
// stall at batch scale.
|
|
117
128
|
const isTimeout =
|
|
118
129
|
e?.name === 'AbortError' || e?.type === 'aborted';
|
|
119
|
-
if (e?.code === 'ECONNRESET' &&
|
|
130
|
+
if (e?.code === 'ECONNRESET' && attempt < this.backOff.length) {
|
|
120
131
|
clearRequestTimer();
|
|
121
|
-
const delay = this.backOff[
|
|
132
|
+
const delay = this.backOff[attempt] * 1000;
|
|
122
133
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
123
|
-
return this._request(url, options,
|
|
134
|
+
return this._request(url, options, attempt + 1);
|
|
124
135
|
}
|
|
125
136
|
const fetchError = await FetchError.create({
|
|
126
137
|
resource: encodedUrl,
|
|
@@ -143,30 +154,57 @@ class Requester extends Delegate {
|
|
|
143
154
|
const { status } = response;
|
|
144
155
|
|
|
145
156
|
// If the status is retriable and there are back off requests left, retry the request
|
|
146
|
-
if (
|
|
157
|
+
if (
|
|
158
|
+
(status === 429 || status >= 500) &&
|
|
159
|
+
attempt < this.backOff.length
|
|
160
|
+
) {
|
|
147
161
|
clearRequestTimer();
|
|
148
|
-
const delay = this.backOff[
|
|
162
|
+
const delay = this.backOff[attempt] * 1000;
|
|
149
163
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
150
|
-
return this._request(url, options,
|
|
164
|
+
return this._request(url, options, attempt + 1);
|
|
151
165
|
}
|
|
152
166
|
|
|
153
167
|
if (status === 401) {
|
|
154
168
|
if (!this.isRefreshable) {
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
// Up to MAX_AUTH_RETRIES grace retries before invalidating
|
|
170
|
+
// — a 401 alone isn't proof the credential is bad.
|
|
171
|
+
if (
|
|
172
|
+
this.authGraceRetryCount < MAX_AUTH_RETRIES &&
|
|
173
|
+
this.authGraceRetryCount < this.backOff.length
|
|
174
|
+
) {
|
|
175
|
+
const delay =
|
|
176
|
+
this.backOff[this.authGraceRetryCount] * 1000;
|
|
177
|
+
this.authGraceRetryCount++;
|
|
178
|
+
clearRequestTimer();
|
|
179
|
+
await new Promise((resolve) =>
|
|
180
|
+
setTimeout(resolve, delay)
|
|
181
|
+
);
|
|
182
|
+
return this._request(url, options, attempt + 1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
throw await this._invalidateAuth(
|
|
186
|
+
encodedUrl,
|
|
187
|
+
options,
|
|
188
|
+
response
|
|
189
|
+
);
|
|
157
190
|
}
|
|
158
191
|
|
|
159
|
-
if (this.refreshCount
|
|
192
|
+
if (this.refreshCount < MAX_AUTH_RETRIES) {
|
|
160
193
|
this.refreshCount++;
|
|
161
194
|
const refreshSucceeded = await this.refreshAuth();
|
|
162
195
|
if (refreshSucceeded) {
|
|
163
196
|
clearRequestTimer();
|
|
164
|
-
return this._request(url, options,
|
|
197
|
+
return this._request(url, options, attempt + 1);
|
|
165
198
|
}
|
|
166
199
|
|
|
167
|
-
await this.
|
|
168
|
-
|
|
200
|
+
throw await this._invalidateAuth(
|
|
201
|
+
encodedUrl,
|
|
202
|
+
options,
|
|
203
|
+
response
|
|
204
|
+
);
|
|
169
205
|
}
|
|
206
|
+
|
|
207
|
+
throw await this._invalidateAuth(encodedUrl, options, response);
|
|
170
208
|
}
|
|
171
209
|
|
|
172
210
|
// If the error wasn't retried, throw. FetchError.create reads
|
|
@@ -188,6 +226,7 @@ class Requester extends Delegate {
|
|
|
188
226
|
// a later 401 in the same Requester lifetime can attempt refresh
|
|
189
227
|
// again instead of silently falling through.
|
|
190
228
|
this.refreshCount = 0;
|
|
229
|
+
this.authGraceRetryCount = 0;
|
|
191
230
|
|
|
192
231
|
// parsedBody consumes the response body stream. If the server
|
|
193
232
|
// stalls mid-stream the timer (still armed) aborts it.
|
|
@@ -204,6 +243,16 @@ class Requester extends Delegate {
|
|
|
204
243
|
}
|
|
205
244
|
}
|
|
206
245
|
|
|
246
|
+
async _invalidateAuth(encodedUrl, options, response) {
|
|
247
|
+
const fetchError = await FetchError.create({
|
|
248
|
+
resource: encodedUrl,
|
|
249
|
+
init: options,
|
|
250
|
+
response,
|
|
251
|
+
});
|
|
252
|
+
await this.notify(this.DLGT_INVALID_AUTH, fetchError);
|
|
253
|
+
return fetchError;
|
|
254
|
+
}
|
|
255
|
+
|
|
207
256
|
_maybeFlagTimeoutDuringBodyRead(err, timeoutMs) {
|
|
208
257
|
if (!err || typeof err !== 'object') return err;
|
|
209
258
|
if (err.isTimeout) return err;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0-next.
|
|
4
|
+
"version": "2.0.0-next.101",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@friggframework/eslint-config": "2.0.0-next.
|
|
42
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
43
|
-
"@friggframework/test": "2.0.0-next.
|
|
41
|
+
"@friggframework/eslint-config": "2.0.0-next.101",
|
|
42
|
+
"@friggframework/prettier-config": "2.0.0-next.101",
|
|
43
|
+
"@friggframework/test": "2.0.0-next.101",
|
|
44
44
|
"@prisma/client": "^6.19.3",
|
|
45
45
|
"@types/lodash": "4.17.15",
|
|
46
46
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "63e9d85c59dc39bb745091911c8149a0de2b3cff"
|
|
84
84
|
}
|