@quiltt/core 5.0.0 → 5.0.2
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/CHANGELOG.md +20 -0
- package/README.md +19 -12
- package/dist/api/browser.cjs +14 -0
- package/dist/api/browser.d.ts +128 -0
- package/dist/api/browser.js +12 -0
- package/dist/api/graphql/SubscriptionLink-12s-ufJBKwu1.js +149 -0
- package/dist/api/graphql/SubscriptionLink-12s-wjkChfxO.cjs +150 -0
- package/dist/api/graphql/index.cjs +218 -0
- package/dist/api/graphql/index.d.ts +82 -0
- package/dist/api/graphql/index.js +184 -0
- package/dist/api/index.cjs +26 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.js +3 -0
- package/dist/api/rest/index.cjs +225 -0
- package/dist/api/rest/index.d.ts +128 -0
- package/dist/api/rest/index.js +217 -0
- package/dist/auth/index.cjs +21 -0
- package/dist/auth/index.d.ts +29 -0
- package/dist/auth/index.js +19 -0
- package/dist/config/index.cjs +44 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.js +36 -0
- package/dist/index.cjs +61 -0
- package/dist/index.d.ts +8 -524
- package/dist/index.js +8 -449
- package/dist/observables/index.cjs +30 -0
- package/dist/observables/index.d.ts +21 -0
- package/dist/observables/index.js +28 -0
- package/dist/storage/index.cjs +272 -0
- package/dist/storage/index.d.ts +91 -0
- package/dist/{SubscriptionLink-12s-C2VbF8Tf.js → storage/index.js} +2 -139
- package/dist/timing/index.cjs +30 -0
- package/dist/timing/index.d.ts +15 -0
- package/dist/timing/index.js +28 -0
- package/dist/types.cjs +1 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.js +1 -0
- package/dist/utils/index.cjs +61 -0
- package/dist/utils/index.d.ts +18 -0
- package/dist/utils/index.js +57 -0
- package/package.json +62 -6
- package/src/api/graphql/client.ts +1 -1
- package/src/api/graphql/links/ActionCableLink.ts +7 -6
- package/src/api/graphql/links/AuthLink.ts +13 -9
- package/src/api/graphql/links/BatchHttpLink.ts +1 -1
- package/src/api/graphql/links/ErrorLink.ts +4 -0
- package/src/api/graphql/links/HttpLink.ts +1 -1
- package/src/api/graphql/links/VersionLink.ts +1 -1
- package/src/api/rest/auth.ts +1 -1
- package/src/api/rest/connectors.ts +1 -1
- package/src/auth/index.ts +1 -0
- package/src/{JsonWebToken.ts → auth/json-web-token.ts} +1 -1
- package/src/{configuration.ts → config/configuration.ts} +1 -1
- package/src/config/index.ts +1 -0
- package/src/index.ts +5 -5
- package/src/observables/index.ts +1 -0
- package/src/{Observable.ts → observables/observable.ts} +1 -1
- package/src/storage/Local.ts +1 -1
- package/src/storage/Memory.ts +2 -2
- package/src/storage/Storage.ts +1 -1
- package/src/timing/index.ts +1 -0
- package/src/{Timeoutable.ts → timing/timeoutable.ts} +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/token-validation.ts +67 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
|
|
3
|
+
var index_cjs = require('../../config/index.cjs');
|
|
4
|
+
var crossfetch = require('cross-fetch');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var crossfetch__default = /*#__PURE__*/_interopDefault(crossfetch);
|
|
9
|
+
|
|
10
|
+
// Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
|
|
11
|
+
const effectiveFetch = typeof fetch === 'undefined' ? crossfetch__default.default : fetch;
|
|
12
|
+
const RETRY_DELAY = 150 // ms
|
|
13
|
+
;
|
|
14
|
+
const RETRIES = 10 // 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500 = 8.250s
|
|
15
|
+
;
|
|
16
|
+
/**
|
|
17
|
+
* A wrapper around the native `fetch` function that adds automatic retries on failure, including network errors and HTTP 429 responses.
|
|
18
|
+
* Now treats any response with status < 500 as valid.
|
|
19
|
+
*/ const fetchWithRetry = async (url, options = {
|
|
20
|
+
retry: false
|
|
21
|
+
})=>{
|
|
22
|
+
const { retry, retriesRemaining, validateStatus = (status)=>status >= 200 && status < 300, ...fetchOptions } = options;
|
|
23
|
+
try {
|
|
24
|
+
const response = await effectiveFetch(url, fetchOptions);
|
|
25
|
+
const isResponseOk = validateStatus(response.status);
|
|
26
|
+
if (isResponseOk) {
|
|
27
|
+
return {
|
|
28
|
+
data: await response.json().catch(()=>null),
|
|
29
|
+
status: response.status,
|
|
30
|
+
statusText: response.statusText,
|
|
31
|
+
headers: response.headers,
|
|
32
|
+
ok: isResponseOk
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// If validateStatus fails, and retry is enabled, prepare to retry for eligible status codes
|
|
36
|
+
if (retry && (response.status >= 500 || response.status === 429)) {
|
|
37
|
+
throw new Error('Retryable failure');
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`HTTP error with status ${response.status}`);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (retry) {
|
|
42
|
+
const currentRetriesRemaining = retriesRemaining !== undefined ? retriesRemaining : RETRIES;
|
|
43
|
+
if (currentRetriesRemaining > 0) {
|
|
44
|
+
const delayTime = RETRY_DELAY * (RETRIES - currentRetriesRemaining);
|
|
45
|
+
await new Promise((resolve)=>setTimeout(resolve, delayTime));
|
|
46
|
+
return fetchWithRetry(url, {
|
|
47
|
+
...options,
|
|
48
|
+
retriesRemaining: currentRetriesRemaining - 1
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Promise.reject(error);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
var AuthStrategies = /*#__PURE__*/ function(AuthStrategies) {
|
|
57
|
+
AuthStrategies["Email"] = "email";
|
|
58
|
+
AuthStrategies["Phone"] = "phone";
|
|
59
|
+
return AuthStrategies;
|
|
60
|
+
}({});
|
|
61
|
+
// https://www.quiltt.dev/api-reference/auth
|
|
62
|
+
class AuthAPI {
|
|
63
|
+
constructor(clientId){
|
|
64
|
+
/**
|
|
65
|
+
* Response Statuses:
|
|
66
|
+
* - 200: OK -> Session is Valid
|
|
67
|
+
* - 401: Unauthorized -> Session is Invalid
|
|
68
|
+
*/ this.ping = async (token)=>{
|
|
69
|
+
const response = await fetchWithRetry(index_cjs.endpointAuth, {
|
|
70
|
+
method: 'GET',
|
|
71
|
+
...this.config(token)
|
|
72
|
+
});
|
|
73
|
+
return response;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Response Statuses:
|
|
77
|
+
* - 201: Created -> Profile Created, New Session Returned
|
|
78
|
+
* - 202: Accepted -> Profile Found, MFA Code Sent for `authenticate`
|
|
79
|
+
* - 422: Unprocessable Entity -> Invalid Payload
|
|
80
|
+
*/ this.identify = async (payload)=>{
|
|
81
|
+
const response = await fetchWithRetry(index_cjs.endpointAuth, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
body: JSON.stringify(this.body(payload)),
|
|
84
|
+
...this.config()
|
|
85
|
+
});
|
|
86
|
+
return response;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Response Statuses:
|
|
90
|
+
* - 201: Created -> MFA Validated, New Session Returned
|
|
91
|
+
* - 401: Unauthorized -> MFA Invalid
|
|
92
|
+
* - 422: Unprocessable Entity -> Invalid Payload
|
|
93
|
+
*/ this.authenticate = async (payload)=>{
|
|
94
|
+
const response = await fetchWithRetry(index_cjs.endpointAuth, {
|
|
95
|
+
method: 'PUT',
|
|
96
|
+
body: JSON.stringify(this.body(payload)),
|
|
97
|
+
...this.config()
|
|
98
|
+
});
|
|
99
|
+
return response;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Response Statuses:
|
|
103
|
+
* - 204: No Content -> Session Revoked
|
|
104
|
+
* - 401: Unauthorized -> Session Not Found
|
|
105
|
+
*/ this.revoke = async (token)=>{
|
|
106
|
+
const response = await fetchWithRetry(index_cjs.endpointAuth, {
|
|
107
|
+
method: 'DELETE',
|
|
108
|
+
...this.config(token)
|
|
109
|
+
});
|
|
110
|
+
return response;
|
|
111
|
+
};
|
|
112
|
+
this.config = (token)=>{
|
|
113
|
+
const headers = new Headers();
|
|
114
|
+
headers.set('Content-Type', 'application/json');
|
|
115
|
+
headers.set('Accept', 'application/json');
|
|
116
|
+
if (token) {
|
|
117
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
headers,
|
|
121
|
+
validateStatus: this.validateStatus,
|
|
122
|
+
retry: true
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
this.validateStatus = (status)=>status < 500 && status !== 429;
|
|
126
|
+
this.body = (payload)=>{
|
|
127
|
+
if (!this.clientId) {
|
|
128
|
+
console.error('Quiltt Client ID is not set. Unable to identify & authenticate');
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
session: {
|
|
132
|
+
clientId: this.clientId,
|
|
133
|
+
...payload
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
this.clientId = clientId;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Extracts version number from formatted version string
|
|
143
|
+
* @param formattedVersion - Formatted version like "@quiltt/core: v4.5.1"
|
|
144
|
+
* @returns Version number like "4.5.1" or "unknown" if not found
|
|
145
|
+
*/ const extractVersionNumber = (formattedVersion)=>{
|
|
146
|
+
// Find the 'v' prefix and extract version after it
|
|
147
|
+
const vIndex = formattedVersion.indexOf('v');
|
|
148
|
+
if (vIndex === -1) return 'unknown';
|
|
149
|
+
const versionPart = formattedVersion.substring(vIndex + 1);
|
|
150
|
+
const parts = versionPart.split('.');
|
|
151
|
+
// Validate we have at least major.minor.patch
|
|
152
|
+
if (parts.length < 3) return 'unknown';
|
|
153
|
+
// Extract numeric parts (handles cases like "4.5.1-beta")
|
|
154
|
+
const major = parts[0].match(/^\d+/)?.[0];
|
|
155
|
+
const minor = parts[1].match(/^\d+/)?.[0];
|
|
156
|
+
const patch = parts[2].match(/^\d+/)?.[0];
|
|
157
|
+
if (!major || !minor || !patch) return 'unknown';
|
|
158
|
+
return `${major}.${minor}.${patch}`;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Generates a User-Agent string following standard format
|
|
162
|
+
* Format: Quiltt/<version> (<platform-info>)
|
|
163
|
+
*/ const getUserAgent = (sdkVersion, platformInfo)=>{
|
|
164
|
+
return `Quiltt/${sdkVersion} (${platformInfo})`;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
class ConnectorsAPI {
|
|
168
|
+
constructor(clientId, userAgent = getUserAgent(extractVersionNumber(index_cjs.version), 'Unknown')){
|
|
169
|
+
/**
|
|
170
|
+
* Response Statuses:
|
|
171
|
+
* - 200: OK -> Institutions Found
|
|
172
|
+
* - 401: Unauthorized -> Invalid Token
|
|
173
|
+
* - 403: Forbidden -> Unsupported SDK
|
|
174
|
+
* - 400: Bad Request -> Invalid Request
|
|
175
|
+
*/ this.searchInstitutions = async (token, connectorId, term, signal)=>{
|
|
176
|
+
const params = new URLSearchParams();
|
|
177
|
+
params.append('term', term);
|
|
178
|
+
const response = await fetchWithRetry(`${index_cjs.endpointRest}/sdk/connectors/${connectorId}/institutions?${params}`, {
|
|
179
|
+
method: 'GET',
|
|
180
|
+
signal,
|
|
181
|
+
...this.config(token)
|
|
182
|
+
});
|
|
183
|
+
return response;
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Response Statuses:
|
|
187
|
+
* - 200: OK -> Provider API ID is resolvable or not
|
|
188
|
+
* - 401: Unauthorized -> Invalid Token
|
|
189
|
+
* - 403: Forbidden -> Unsupported SDK
|
|
190
|
+
* - 400: Bad Request -> Missing provider API ID parameter
|
|
191
|
+
* - 404: Not Found -> Connector not found
|
|
192
|
+
*/ this.checkResolvable = async (token, connectorId, providerId, signal)=>{
|
|
193
|
+
const params = new URLSearchParams();
|
|
194
|
+
const providerKey = Object.keys(providerId)[0];
|
|
195
|
+
if (providerKey && providerId[providerKey]) {
|
|
196
|
+
params.append(providerKey, providerId[providerKey]);
|
|
197
|
+
}
|
|
198
|
+
const response = await fetchWithRetry(`${index_cjs.endpointRest}/sdk/connectors/${connectorId}/resolvable?${params}`, {
|
|
199
|
+
method: 'GET',
|
|
200
|
+
signal,
|
|
201
|
+
...this.config(token)
|
|
202
|
+
});
|
|
203
|
+
return response;
|
|
204
|
+
};
|
|
205
|
+
this.config = (token)=>{
|
|
206
|
+
const headers = new Headers();
|
|
207
|
+
headers.set('Content-Type', 'application/json');
|
|
208
|
+
headers.set('Accept', 'application/json');
|
|
209
|
+
headers.set('User-Agent', this.userAgent);
|
|
210
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
211
|
+
return {
|
|
212
|
+
headers,
|
|
213
|
+
validateStatus: this.validateStatus,
|
|
214
|
+
retry: true
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
this.validateStatus = (status)=>status < 500 && status !== 429;
|
|
218
|
+
this.clientId = clientId;
|
|
219
|
+
this.userAgent = userAgent;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
exports.AuthAPI = AuthAPI;
|
|
224
|
+
exports.AuthStrategies = AuthStrategies;
|
|
225
|
+
exports.ConnectorsAPI = ConnectorsAPI;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
type FetchResponse<T> = {
|
|
2
|
+
data: T;
|
|
3
|
+
status: number;
|
|
4
|
+
statusText: string;
|
|
5
|
+
headers: Headers;
|
|
6
|
+
ok: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type NoContentData = null;
|
|
10
|
+
type ErrorData = {
|
|
11
|
+
message: string;
|
|
12
|
+
instruction: string;
|
|
13
|
+
error_id: string;
|
|
14
|
+
};
|
|
15
|
+
type UnauthorizedData = {
|
|
16
|
+
message: string;
|
|
17
|
+
instruction: string;
|
|
18
|
+
};
|
|
19
|
+
type UnprocessableData = {
|
|
20
|
+
[attribute: string]: Array<string>;
|
|
21
|
+
};
|
|
22
|
+
type BadRequestResponse = FetchResponse<ErrorData>;
|
|
23
|
+
type UnauthorizedResponse = FetchResponse<UnauthorizedData>;
|
|
24
|
+
type UnprocessableResponse = FetchResponse<UnprocessableData>;
|
|
25
|
+
|
|
26
|
+
declare enum AuthStrategies {
|
|
27
|
+
Email = "email",
|
|
28
|
+
Phone = "phone"
|
|
29
|
+
}
|
|
30
|
+
interface EmailInput {
|
|
31
|
+
email: string;
|
|
32
|
+
phone?: never;
|
|
33
|
+
}
|
|
34
|
+
interface PhoneInput {
|
|
35
|
+
phone: string;
|
|
36
|
+
email?: never;
|
|
37
|
+
}
|
|
38
|
+
type UsernamePayload = EmailInput | PhoneInput;
|
|
39
|
+
type PasscodePayload = UsernamePayload & {
|
|
40
|
+
passcode: string;
|
|
41
|
+
};
|
|
42
|
+
type SessionData = {
|
|
43
|
+
token: string;
|
|
44
|
+
};
|
|
45
|
+
type Ping = SessionData | UnauthorizedData;
|
|
46
|
+
type Identify = SessionData | NoContentData | UnprocessableData;
|
|
47
|
+
type Authenticate = SessionData | UnauthorizedData | UnprocessableData;
|
|
48
|
+
type Revoke = NoContentData | UnauthorizedData;
|
|
49
|
+
type SessionResponse = FetchResponse<SessionData>;
|
|
50
|
+
declare class AuthAPI {
|
|
51
|
+
clientId: string | undefined;
|
|
52
|
+
constructor(clientId?: string | undefined);
|
|
53
|
+
/**
|
|
54
|
+
* Response Statuses:
|
|
55
|
+
* - 200: OK -> Session is Valid
|
|
56
|
+
* - 401: Unauthorized -> Session is Invalid
|
|
57
|
+
*/
|
|
58
|
+
ping: (token: string) => Promise<FetchResponse<Ping>>;
|
|
59
|
+
/**
|
|
60
|
+
* Response Statuses:
|
|
61
|
+
* - 201: Created -> Profile Created, New Session Returned
|
|
62
|
+
* - 202: Accepted -> Profile Found, MFA Code Sent for `authenticate`
|
|
63
|
+
* - 422: Unprocessable Entity -> Invalid Payload
|
|
64
|
+
*/
|
|
65
|
+
identify: (payload: UsernamePayload) => Promise<FetchResponse<Identify>>;
|
|
66
|
+
/**
|
|
67
|
+
* Response Statuses:
|
|
68
|
+
* - 201: Created -> MFA Validated, New Session Returned
|
|
69
|
+
* - 401: Unauthorized -> MFA Invalid
|
|
70
|
+
* - 422: Unprocessable Entity -> Invalid Payload
|
|
71
|
+
*/
|
|
72
|
+
authenticate: (payload: PasscodePayload) => Promise<FetchResponse<Authenticate>>;
|
|
73
|
+
/**
|
|
74
|
+
* Response Statuses:
|
|
75
|
+
* - 204: No Content -> Session Revoked
|
|
76
|
+
* - 401: Unauthorized -> Session Not Found
|
|
77
|
+
*/
|
|
78
|
+
revoke: (token: string) => Promise<FetchResponse<Revoke>>;
|
|
79
|
+
private config;
|
|
80
|
+
private validateStatus;
|
|
81
|
+
private body;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type InstitutionData = {
|
|
85
|
+
name: string;
|
|
86
|
+
logoUrl: string;
|
|
87
|
+
};
|
|
88
|
+
type InstitutionsData = Array<InstitutionData>;
|
|
89
|
+
type ResolvableData = {
|
|
90
|
+
resolvable: boolean;
|
|
91
|
+
};
|
|
92
|
+
type Search = InstitutionsData | ErrorData | UnauthorizedData;
|
|
93
|
+
type Resolvable = ResolvableData | ErrorData | UnauthorizedData;
|
|
94
|
+
type SearchResponse = FetchResponse<InstitutionsData>;
|
|
95
|
+
type ResolvableResponse = FetchResponse<ResolvableData>;
|
|
96
|
+
declare class ConnectorsAPI {
|
|
97
|
+
clientId: string;
|
|
98
|
+
userAgent: string;
|
|
99
|
+
constructor(clientId: string, userAgent?: string);
|
|
100
|
+
/**
|
|
101
|
+
* Response Statuses:
|
|
102
|
+
* - 200: OK -> Institutions Found
|
|
103
|
+
* - 401: Unauthorized -> Invalid Token
|
|
104
|
+
* - 403: Forbidden -> Unsupported SDK
|
|
105
|
+
* - 400: Bad Request -> Invalid Request
|
|
106
|
+
*/
|
|
107
|
+
searchInstitutions: (token: string, connectorId: string, term: string, signal?: AbortSignal) => Promise<FetchResponse<Search>>;
|
|
108
|
+
/**
|
|
109
|
+
* Response Statuses:
|
|
110
|
+
* - 200: OK -> Provider API ID is resolvable or not
|
|
111
|
+
* - 401: Unauthorized -> Invalid Token
|
|
112
|
+
* - 403: Forbidden -> Unsupported SDK
|
|
113
|
+
* - 400: Bad Request -> Missing provider API ID parameter
|
|
114
|
+
* - 404: Not Found -> Connector not found
|
|
115
|
+
*/
|
|
116
|
+
checkResolvable: (token: string, connectorId: string, providerId: {
|
|
117
|
+
plaid?: string;
|
|
118
|
+
mock?: string;
|
|
119
|
+
mx?: string;
|
|
120
|
+
finicity?: string;
|
|
121
|
+
akoya?: string;
|
|
122
|
+
}, signal?: AbortSignal) => Promise<FetchResponse<Resolvable>>;
|
|
123
|
+
private config;
|
|
124
|
+
private validateStatus;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { AuthAPI, AuthStrategies, ConnectorsAPI };
|
|
128
|
+
export type { BadRequestResponse, ErrorData, InstitutionData, InstitutionsData, NoContentData, PasscodePayload, ResolvableData, ResolvableResponse, SearchResponse, SessionResponse, UnauthorizedData, UnauthorizedResponse, UnprocessableData, UnprocessableResponse, UsernamePayload };
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { endpointAuth, endpointRest, version } from '../../config/index.js';
|
|
2
|
+
import crossfetch from 'cross-fetch';
|
|
3
|
+
|
|
4
|
+
// Use `cross-fetch` only if `fetch` is not available on the `globalThis` object
|
|
5
|
+
const effectiveFetch = typeof fetch === 'undefined' ? crossfetch : fetch;
|
|
6
|
+
const RETRY_DELAY = 150 // ms
|
|
7
|
+
;
|
|
8
|
+
const RETRIES = 10 // 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500 = 8.250s
|
|
9
|
+
;
|
|
10
|
+
/**
|
|
11
|
+
* A wrapper around the native `fetch` function that adds automatic retries on failure, including network errors and HTTP 429 responses.
|
|
12
|
+
* Now treats any response with status < 500 as valid.
|
|
13
|
+
*/ const fetchWithRetry = async (url, options = {
|
|
14
|
+
retry: false
|
|
15
|
+
})=>{
|
|
16
|
+
const { retry, retriesRemaining, validateStatus = (status)=>status >= 200 && status < 300, ...fetchOptions } = options;
|
|
17
|
+
try {
|
|
18
|
+
const response = await effectiveFetch(url, fetchOptions);
|
|
19
|
+
const isResponseOk = validateStatus(response.status);
|
|
20
|
+
if (isResponseOk) {
|
|
21
|
+
return {
|
|
22
|
+
data: await response.json().catch(()=>null),
|
|
23
|
+
status: response.status,
|
|
24
|
+
statusText: response.statusText,
|
|
25
|
+
headers: response.headers,
|
|
26
|
+
ok: isResponseOk
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// If validateStatus fails, and retry is enabled, prepare to retry for eligible status codes
|
|
30
|
+
if (retry && (response.status >= 500 || response.status === 429)) {
|
|
31
|
+
throw new Error('Retryable failure');
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`HTTP error with status ${response.status}`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (retry) {
|
|
36
|
+
const currentRetriesRemaining = retriesRemaining !== undefined ? retriesRemaining : RETRIES;
|
|
37
|
+
if (currentRetriesRemaining > 0) {
|
|
38
|
+
const delayTime = RETRY_DELAY * (RETRIES - currentRetriesRemaining);
|
|
39
|
+
await new Promise((resolve)=>setTimeout(resolve, delayTime));
|
|
40
|
+
return fetchWithRetry(url, {
|
|
41
|
+
...options,
|
|
42
|
+
retriesRemaining: currentRetriesRemaining - 1
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return Promise.reject(error);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
var AuthStrategies = /*#__PURE__*/ function(AuthStrategies) {
|
|
51
|
+
AuthStrategies["Email"] = "email";
|
|
52
|
+
AuthStrategies["Phone"] = "phone";
|
|
53
|
+
return AuthStrategies;
|
|
54
|
+
}({});
|
|
55
|
+
// https://www.quiltt.dev/api-reference/auth
|
|
56
|
+
class AuthAPI {
|
|
57
|
+
constructor(clientId){
|
|
58
|
+
/**
|
|
59
|
+
* Response Statuses:
|
|
60
|
+
* - 200: OK -> Session is Valid
|
|
61
|
+
* - 401: Unauthorized -> Session is Invalid
|
|
62
|
+
*/ this.ping = async (token)=>{
|
|
63
|
+
const response = await fetchWithRetry(endpointAuth, {
|
|
64
|
+
method: 'GET',
|
|
65
|
+
...this.config(token)
|
|
66
|
+
});
|
|
67
|
+
return response;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Response Statuses:
|
|
71
|
+
* - 201: Created -> Profile Created, New Session Returned
|
|
72
|
+
* - 202: Accepted -> Profile Found, MFA Code Sent for `authenticate`
|
|
73
|
+
* - 422: Unprocessable Entity -> Invalid Payload
|
|
74
|
+
*/ this.identify = async (payload)=>{
|
|
75
|
+
const response = await fetchWithRetry(endpointAuth, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
body: JSON.stringify(this.body(payload)),
|
|
78
|
+
...this.config()
|
|
79
|
+
});
|
|
80
|
+
return response;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Response Statuses:
|
|
84
|
+
* - 201: Created -> MFA Validated, New Session Returned
|
|
85
|
+
* - 401: Unauthorized -> MFA Invalid
|
|
86
|
+
* - 422: Unprocessable Entity -> Invalid Payload
|
|
87
|
+
*/ this.authenticate = async (payload)=>{
|
|
88
|
+
const response = await fetchWithRetry(endpointAuth, {
|
|
89
|
+
method: 'PUT',
|
|
90
|
+
body: JSON.stringify(this.body(payload)),
|
|
91
|
+
...this.config()
|
|
92
|
+
});
|
|
93
|
+
return response;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Response Statuses:
|
|
97
|
+
* - 204: No Content -> Session Revoked
|
|
98
|
+
* - 401: Unauthorized -> Session Not Found
|
|
99
|
+
*/ this.revoke = async (token)=>{
|
|
100
|
+
const response = await fetchWithRetry(endpointAuth, {
|
|
101
|
+
method: 'DELETE',
|
|
102
|
+
...this.config(token)
|
|
103
|
+
});
|
|
104
|
+
return response;
|
|
105
|
+
};
|
|
106
|
+
this.config = (token)=>{
|
|
107
|
+
const headers = new Headers();
|
|
108
|
+
headers.set('Content-Type', 'application/json');
|
|
109
|
+
headers.set('Accept', 'application/json');
|
|
110
|
+
if (token) {
|
|
111
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
headers,
|
|
115
|
+
validateStatus: this.validateStatus,
|
|
116
|
+
retry: true
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
this.validateStatus = (status)=>status < 500 && status !== 429;
|
|
120
|
+
this.body = (payload)=>{
|
|
121
|
+
if (!this.clientId) {
|
|
122
|
+
console.error('Quiltt Client ID is not set. Unable to identify & authenticate');
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
session: {
|
|
126
|
+
clientId: this.clientId,
|
|
127
|
+
...payload
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
this.clientId = clientId;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Extracts version number from formatted version string
|
|
137
|
+
* @param formattedVersion - Formatted version like "@quiltt/core: v4.5.1"
|
|
138
|
+
* @returns Version number like "4.5.1" or "unknown" if not found
|
|
139
|
+
*/ const extractVersionNumber = (formattedVersion)=>{
|
|
140
|
+
// Find the 'v' prefix and extract version after it
|
|
141
|
+
const vIndex = formattedVersion.indexOf('v');
|
|
142
|
+
if (vIndex === -1) return 'unknown';
|
|
143
|
+
const versionPart = formattedVersion.substring(vIndex + 1);
|
|
144
|
+
const parts = versionPart.split('.');
|
|
145
|
+
// Validate we have at least major.minor.patch
|
|
146
|
+
if (parts.length < 3) return 'unknown';
|
|
147
|
+
// Extract numeric parts (handles cases like "4.5.1-beta")
|
|
148
|
+
const major = parts[0].match(/^\d+/)?.[0];
|
|
149
|
+
const minor = parts[1].match(/^\d+/)?.[0];
|
|
150
|
+
const patch = parts[2].match(/^\d+/)?.[0];
|
|
151
|
+
if (!major || !minor || !patch) return 'unknown';
|
|
152
|
+
return `${major}.${minor}.${patch}`;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Generates a User-Agent string following standard format
|
|
156
|
+
* Format: Quiltt/<version> (<platform-info>)
|
|
157
|
+
*/ const getUserAgent = (sdkVersion, platformInfo)=>{
|
|
158
|
+
return `Quiltt/${sdkVersion} (${platformInfo})`;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
class ConnectorsAPI {
|
|
162
|
+
constructor(clientId, userAgent = getUserAgent(extractVersionNumber(version), 'Unknown')){
|
|
163
|
+
/**
|
|
164
|
+
* Response Statuses:
|
|
165
|
+
* - 200: OK -> Institutions Found
|
|
166
|
+
* - 401: Unauthorized -> Invalid Token
|
|
167
|
+
* - 403: Forbidden -> Unsupported SDK
|
|
168
|
+
* - 400: Bad Request -> Invalid Request
|
|
169
|
+
*/ this.searchInstitutions = async (token, connectorId, term, signal)=>{
|
|
170
|
+
const params = new URLSearchParams();
|
|
171
|
+
params.append('term', term);
|
|
172
|
+
const response = await fetchWithRetry(`${endpointRest}/sdk/connectors/${connectorId}/institutions?${params}`, {
|
|
173
|
+
method: 'GET',
|
|
174
|
+
signal,
|
|
175
|
+
...this.config(token)
|
|
176
|
+
});
|
|
177
|
+
return response;
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Response Statuses:
|
|
181
|
+
* - 200: OK -> Provider API ID is resolvable or not
|
|
182
|
+
* - 401: Unauthorized -> Invalid Token
|
|
183
|
+
* - 403: Forbidden -> Unsupported SDK
|
|
184
|
+
* - 400: Bad Request -> Missing provider API ID parameter
|
|
185
|
+
* - 404: Not Found -> Connector not found
|
|
186
|
+
*/ this.checkResolvable = async (token, connectorId, providerId, signal)=>{
|
|
187
|
+
const params = new URLSearchParams();
|
|
188
|
+
const providerKey = Object.keys(providerId)[0];
|
|
189
|
+
if (providerKey && providerId[providerKey]) {
|
|
190
|
+
params.append(providerKey, providerId[providerKey]);
|
|
191
|
+
}
|
|
192
|
+
const response = await fetchWithRetry(`${endpointRest}/sdk/connectors/${connectorId}/resolvable?${params}`, {
|
|
193
|
+
method: 'GET',
|
|
194
|
+
signal,
|
|
195
|
+
...this.config(token)
|
|
196
|
+
});
|
|
197
|
+
return response;
|
|
198
|
+
};
|
|
199
|
+
this.config = (token)=>{
|
|
200
|
+
const headers = new Headers();
|
|
201
|
+
headers.set('Content-Type', 'application/json');
|
|
202
|
+
headers.set('Accept', 'application/json');
|
|
203
|
+
headers.set('User-Agent', this.userAgent);
|
|
204
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
205
|
+
return {
|
|
206
|
+
headers,
|
|
207
|
+
validateStatus: this.validateStatus,
|
|
208
|
+
retry: true
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
this.validateStatus = (status)=>status < 500 && status !== 429;
|
|
212
|
+
this.clientId = clientId;
|
|
213
|
+
this.userAgent = userAgent;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export { AuthAPI, AuthStrategies, ConnectorsAPI };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
|
|
3
|
+
const MATCHER = /^(?:[\w-]+\.){2}[\w-]+$/;
|
|
4
|
+
const JsonWebTokenParse = (token)=>{
|
|
5
|
+
if (typeof token === 'undefined' || token === null) return token;
|
|
6
|
+
if (!MATCHER.test(token)) {
|
|
7
|
+
console.error(`Invalid Session Token: ${token}`);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const [_header, payload, _signature] = token.split('.');
|
|
11
|
+
try {
|
|
12
|
+
return {
|
|
13
|
+
token,
|
|
14
|
+
claims: JSON.parse(atob(payload))
|
|
15
|
+
};
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error(`Invalid Session Token: ${error}`);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
exports.JsonWebTokenParse = JsonWebTokenParse;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Maybe } from '../types.js';
|
|
2
|
+
|
|
3
|
+
type RegisteredClaims = {
|
|
4
|
+
iss: string;
|
|
5
|
+
sub: string;
|
|
6
|
+
aud: string;
|
|
7
|
+
exp: number;
|
|
8
|
+
nbf: number;
|
|
9
|
+
iat: number;
|
|
10
|
+
jti: string;
|
|
11
|
+
};
|
|
12
|
+
type PrivateClaims = {
|
|
13
|
+
oid: string;
|
|
14
|
+
eid: string;
|
|
15
|
+
cid: string;
|
|
16
|
+
aid: string;
|
|
17
|
+
ver: number;
|
|
18
|
+
rol: 'basic' | 'core' | 'manager' | 'super-admin';
|
|
19
|
+
};
|
|
20
|
+
type Claims<T> = RegisteredClaims & T;
|
|
21
|
+
type JsonWebToken<T> = {
|
|
22
|
+
token: string;
|
|
23
|
+
claims: Claims<T>;
|
|
24
|
+
};
|
|
25
|
+
type QuilttJWT = JsonWebToken<PrivateClaims>;
|
|
26
|
+
declare const JsonWebTokenParse: <T>(token: Maybe<string> | undefined) => Maybe<JsonWebToken<T>> | undefined;
|
|
27
|
+
|
|
28
|
+
export { JsonWebTokenParse };
|
|
29
|
+
export type { Claims, JsonWebToken, PrivateClaims, QuilttJWT, RegisteredClaims };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const MATCHER = /^(?:[\w-]+\.){2}[\w-]+$/;
|
|
2
|
+
const JsonWebTokenParse = (token)=>{
|
|
3
|
+
if (typeof token === 'undefined' || token === null) return token;
|
|
4
|
+
if (!MATCHER.test(token)) {
|
|
5
|
+
console.error(`Invalid Session Token: ${token}`);
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const [_header, payload, _signature] = token.split('.');
|
|
9
|
+
try {
|
|
10
|
+
return {
|
|
11
|
+
token,
|
|
12
|
+
claims: JSON.parse(atob(payload))
|
|
13
|
+
};
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`Invalid Session Token: ${error}`);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export { JsonWebTokenParse };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
|
|
3
|
+
var name = "@quiltt/core";
|
|
4
|
+
var version$1 = "5.0.2";
|
|
5
|
+
|
|
6
|
+
const QUILTT_API_INSECURE = (()=>{
|
|
7
|
+
try {
|
|
8
|
+
return process.env.QUILTT_API_INSECURE === 'true';
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
})();
|
|
13
|
+
const QUILTT_API_DOMAIN = (()=>{
|
|
14
|
+
try {
|
|
15
|
+
return process.env.QUILTT_API_DOMAIN;
|
|
16
|
+
} catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
const QUILTT_DEBUG = (()=>{
|
|
21
|
+
try {
|
|
22
|
+
return process.env.NODE_ENV !== 'production' && process.env.QUILTT_DEBUG === 'true';
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
})();
|
|
27
|
+
const domain = QUILTT_API_DOMAIN || 'quiltt.io';
|
|
28
|
+
const protocolHttp = `http${QUILTT_API_INSECURE ? '' : 's'}`;
|
|
29
|
+
const protocolWebsockets = `ws${QUILTT_API_INSECURE ? '' : 's'}`;
|
|
30
|
+
const debugging = QUILTT_DEBUG;
|
|
31
|
+
const version = `${name}: v${version$1}`;
|
|
32
|
+
const cdnBase = `${protocolHttp}://cdn.${domain}`;
|
|
33
|
+
const endpointAuth = `${protocolHttp}://auth.${domain}/v1/users/session`;
|
|
34
|
+
const endpointGraphQL = `${protocolHttp}://api.${domain}/v1/graphql`;
|
|
35
|
+
const endpointRest = `${protocolHttp}://api.${domain}/v1`;
|
|
36
|
+
const endpointWebsockets = `${protocolWebsockets}://api.${domain}/websockets`;
|
|
37
|
+
|
|
38
|
+
exports.cdnBase = cdnBase;
|
|
39
|
+
exports.debugging = debugging;
|
|
40
|
+
exports.endpointAuth = endpointAuth;
|
|
41
|
+
exports.endpointGraphQL = endpointGraphQL;
|
|
42
|
+
exports.endpointRest = endpointRest;
|
|
43
|
+
exports.endpointWebsockets = endpointWebsockets;
|
|
44
|
+
exports.version = version;
|