joopjs 2.0.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/CHANGELOG.md +678 -0
- package/README.md +583 -0
- package/dist/a11y.service-C-DQQfgO.d.mts +143 -0
- package/dist/a11y.service-CauEJrJe.d.ts +143 -0
- package/dist/adapters-B6slG6hQ.d.mts +84 -0
- package/dist/adapters-B6slG6hQ.d.ts +84 -0
- package/dist/aes.service-CkoupAww.d.mts +95 -0
- package/dist/aes.service-CkoupAww.d.ts +95 -0
- package/dist/ai/index.d.mts +99 -0
- package/dist/ai/index.d.ts +99 -0
- package/dist/ai/index.js +307 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/index.mjs +304 -0
- package/dist/ai/index.mjs.map +1 -0
- package/dist/analytics/index.d.mts +42 -0
- package/dist/analytics/index.d.ts +42 -0
- package/dist/analytics/index.js +139 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/index.mjs +136 -0
- package/dist/analytics/index.mjs.map +1 -0
- package/dist/angular/index.d.mts +148 -0
- package/dist/angular/index.d.ts +148 -0
- package/dist/angular/index.js +122 -0
- package/dist/angular/index.js.map +1 -0
- package/dist/angular/index.mjs +101 -0
- package/dist/angular/index.mjs.map +1 -0
- package/dist/api/index.d.mts +128 -0
- package/dist/api/index.d.ts +128 -0
- package/dist/api/index.js +1358 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/index.mjs +1332 -0
- package/dist/api/index.mjs.map +1 -0
- package/dist/auth/index.d.mts +105 -0
- package/dist/auth/index.d.ts +105 -0
- package/dist/auth/index.js +989 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/index.mjs +979 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/auth.service-DNVB-L4U.d.mts +16 -0
- package/dist/auth.service-PjUUSUIt.d.ts +16 -0
- package/dist/banking/index.d.mts +1530 -0
- package/dist/banking/index.d.ts +1530 -0
- package/dist/banking/index.js +4739 -0
- package/dist/banking/index.js.map +1 -0
- package/dist/banking/index.mjs +4661 -0
- package/dist/banking/index.mjs.map +1 -0
- package/dist/cache/index.d.mts +40 -0
- package/dist/cache/index.d.ts +40 -0
- package/dist/cache/index.js +174 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/index.mjs +172 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/client-profile.service-BuPeXVp5.d.mts +28 -0
- package/dist/client-profile.service-D5bRRYQp.d.ts +28 -0
- package/dist/config.models-Cqg04fAQ.d.mts +84 -0
- package/dist/config.models-Cqg04fAQ.d.ts +84 -0
- package/dist/config.service-CrCvI-JS.d.ts +31 -0
- package/dist/config.service-Cz4QQLlf.d.mts +31 -0
- package/dist/core/index.d.mts +4 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +631 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +619 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/crypto-utils-DriNhLdx.d.mts +30 -0
- package/dist/crypto-utils-DriNhLdx.d.ts +30 -0
- package/dist/data-storage.service-DT6xaTxE.d.ts +51 -0
- package/dist/data-storage.service-LvhGRCmw.d.mts +51 -0
- package/dist/deeplink/index.d.mts +39 -0
- package/dist/deeplink/index.d.ts +39 -0
- package/dist/deeplink/index.js +268 -0
- package/dist/deeplink/index.js.map +1 -0
- package/dist/deeplink/index.mjs +265 -0
- package/dist/deeplink/index.mjs.map +1 -0
- package/dist/deeplink.service-Ctd5u243.d.mts +35 -0
- package/dist/deeplink.service-uUuTnY9_.d.ts +35 -0
- package/dist/dev/index.d.mts +20 -0
- package/dist/dev/index.d.ts +20 -0
- package/dist/dev/index.js +51 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/index.mjs +49 -0
- package/dist/dev/index.mjs.map +1 -0
- package/dist/device/index.d.mts +108 -0
- package/dist/device/index.d.ts +108 -0
- package/dist/device/index.js +960 -0
- package/dist/device/index.js.map +1 -0
- package/dist/device/index.mjs +951 -0
- package/dist/device/index.mjs.map +1 -0
- package/dist/differential-privacy-BcAv1G80.d.mts +210 -0
- package/dist/differential-privacy-C8mAUjZr.d.ts +210 -0
- package/dist/encryption/index.d.mts +75 -0
- package/dist/encryption/index.d.ts +75 -0
- package/dist/encryption/index.js +605 -0
- package/dist/encryption/index.js.map +1 -0
- package/dist/encryption/index.mjs +598 -0
- package/dist/encryption/index.mjs.map +1 -0
- package/dist/form-validator-3tkmzr_o.d.mts +72 -0
- package/dist/form-validator-3tkmzr_o.d.ts +72 -0
- package/dist/forms/index.d.mts +59 -0
- package/dist/forms/index.d.ts +59 -0
- package/dist/forms/index.js +446 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/forms/index.mjs +442 -0
- package/dist/forms/index.mjs.map +1 -0
- package/dist/i18n/index.d.mts +37 -0
- package/dist/i18n/index.d.ts +37 -0
- package/dist/i18n/index.js +147 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/index.mjs +145 -0
- package/dist/i18n/index.mjs.map +1 -0
- package/dist/idempotency.service-_6LqhivP.d.mts +372 -0
- package/dist/idempotency.service-eOKoISRD.d.ts +372 -0
- package/dist/index-B_ksKpS1.d.mts +202 -0
- package/dist/index-CqDKWTUP.d.mts +28 -0
- package/dist/index-CqDKWTUP.d.ts +28 -0
- package/dist/index-DFqEoX_l.d.ts +202 -0
- package/dist/index-Dz0gOur2.d.mts +36 -0
- package/dist/index-Dz0gOur2.d.ts +36 -0
- package/dist/index.d.mts +1336 -0
- package/dist/index.d.ts +1336 -0
- package/dist/index.js +19464 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +19155 -0
- package/dist/index.mjs.map +1 -0
- package/dist/india/index.d.mts +75 -0
- package/dist/india/index.d.ts +75 -0
- package/dist/india/index.js +325 -0
- package/dist/india/index.js.map +1 -0
- package/dist/india/index.mjs +303 -0
- package/dist/india/index.mjs.map +1 -0
- package/dist/joop-Bx7Iwj5p.d.mts +155 -0
- package/dist/joop-CA3DMeOO.d.ts +155 -0
- package/dist/native-bridge/index.d.mts +27 -0
- package/dist/native-bridge/index.d.ts +27 -0
- package/dist/native-bridge/index.js +98 -0
- package/dist/native-bridge/index.js.map +1 -0
- package/dist/native-bridge/index.mjs +96 -0
- package/dist/native-bridge/index.mjs.map +1 -0
- package/dist/network/index.d.mts +85 -0
- package/dist/network/index.d.ts +85 -0
- package/dist/network/index.js +454 -0
- package/dist/network/index.js.map +1 -0
- package/dist/network/index.mjs +451 -0
- package/dist/network/index.mjs.map +1 -0
- package/dist/network-monitor-BIwPSXme.d.mts +179 -0
- package/dist/network-monitor-Bqp2hvZr.d.ts +179 -0
- package/dist/notification.service-Dm4fvfZf.d.mts +25 -0
- package/dist/notification.service-tEMKatWJ.d.ts +25 -0
- package/dist/observability/index.d.mts +179 -0
- package/dist/observability/index.d.ts +179 -0
- package/dist/observability/index.js +559 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/index.mjs +552 -0
- package/dist/observability/index.mjs.map +1 -0
- package/dist/oidc-client-DIJcClmB.d.mts +190 -0
- package/dist/oidc-client-DxhyE59t.d.ts +190 -0
- package/dist/platform/index.d.mts +73 -0
- package/dist/platform/index.d.ts +73 -0
- package/dist/platform/index.js +127 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/index.mjs +125 -0
- package/dist/platform/index.mjs.map +1 -0
- package/dist/pwa/index.d.mts +31 -0
- package/dist/pwa/index.d.ts +31 -0
- package/dist/pwa/index.js +247 -0
- package/dist/pwa/index.js.map +1 -0
- package/dist/pwa/index.mjs +244 -0
- package/dist/pwa/index.mjs.map +1 -0
- package/dist/react/index.d.mts +133 -0
- package/dist/react/index.d.ts +133 -0
- package/dist/react/index.js +632 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +630 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/router/index.d.mts +39 -0
- package/dist/router/index.d.ts +39 -0
- package/dist/router/index.js +168 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/index.mjs +166 -0
- package/dist/router/index.mjs.map +1 -0
- package/dist/security/index.d.mts +206 -0
- package/dist/security/index.d.ts +206 -0
- package/dist/security/index.js +1297 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/index.mjs +1285 -0
- package/dist/security/index.mjs.map +1 -0
- package/dist/session/index.d.mts +115 -0
- package/dist/session/index.d.ts +115 -0
- package/dist/session/index.js +297 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/index.mjs +292 -0
- package/dist/session/index.mjs.map +1 -0
- package/dist/state/index.d.mts +43 -0
- package/dist/state/index.d.ts +43 -0
- package/dist/state/index.js +156 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/index.mjs +152 -0
- package/dist/state/index.mjs.map +1 -0
- package/dist/statement-parser-BHQtXwCM.d.ts +260 -0
- package/dist/statement-parser-C2qNmb49.d.mts +260 -0
- package/dist/storage/index.d.mts +40 -0
- package/dist/storage/index.d.ts +40 -0
- package/dist/storage/index.js +256 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +252 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/sync/index.d.mts +69 -0
- package/dist/sync/index.d.ts +69 -0
- package/dist/sync/index.js +330 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/index.mjs +323 -0
- package/dist/sync/index.mjs.map +1 -0
- package/dist/sync-engine-DCIMRG5s.d.ts +61 -0
- package/dist/sync-engine-DZqyKHkK.d.mts +61 -0
- package/dist/theme/index.d.mts +53 -0
- package/dist/theme/index.d.ts +53 -0
- package/dist/theme/index.js +169 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/index.mjs +167 -0
- package/dist/theme/index.mjs.map +1 -0
- package/dist/ui/index.d.mts +66 -0
- package/dist/ui/index.d.ts +66 -0
- package/dist/ui/index.js +811 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +803 -0
- package/dist/ui/index.mjs.map +1 -0
- package/dist/utilities/index.d.mts +199 -0
- package/dist/utilities/index.d.ts +199 -0
- package/dist/utilities/index.js +1991 -0
- package/dist/utilities/index.js.map +1 -0
- package/dist/utilities/index.mjs +1923 -0
- package/dist/utilities/index.mjs.map +1 -0
- package/dist/validation/index.d.mts +60 -0
- package/dist/validation/index.d.ts +60 -0
- package/dist/validation/index.js +460 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/index.mjs +455 -0
- package/dist/validation/index.mjs.map +1 -0
- package/dist/vue/index.d.mts +135 -0
- package/dist/vue/index.d.ts +135 -0
- package/dist/vue/index.js +621 -0
- package/dist/vue/index.js.map +1 -0
- package/dist/vue/index.mjs +619 -0
- package/dist/vue/index.mjs.map +1 -0
- package/dist/watermark.service-Detur5tq.d.ts +235 -0
- package/dist/watermark.service-QNegMeQZ.d.mts +235 -0
- package/dist/workers/index.d.mts +42 -0
- package/dist/workers/index.d.ts +42 -0
- package/dist/workers/index.js +359 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/index.mjs +356 -0
- package/dist/workers/index.mjs.map +1 -0
- package/dist/workflow/index.d.mts +99 -0
- package/dist/workflow/index.d.ts +99 -0
- package/dist/workflow/index.js +282 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/index.mjs +279 -0
- package/dist/workflow/index.mjs.map +1 -0
- package/package.json +226 -0
|
@@ -0,0 +1,1332 @@
|
|
|
1
|
+
// src/models/error.models.ts
|
|
2
|
+
var JoopHttpError = class extends Error {
|
|
3
|
+
constructor(statusCode, statusText, body) {
|
|
4
|
+
super(`HTTP ${statusCode}: ${statusText}`);
|
|
5
|
+
this.statusCode = statusCode;
|
|
6
|
+
this.statusText = statusText;
|
|
7
|
+
this.body = body;
|
|
8
|
+
this.name = "JoopHttpError";
|
|
9
|
+
}
|
|
10
|
+
statusCode;
|
|
11
|
+
statusText;
|
|
12
|
+
body;
|
|
13
|
+
};
|
|
14
|
+
var JoopEncryptionError = class extends Error {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "JoopEncryptionError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/api/http/http-client.ts
|
|
22
|
+
var JoopHttpClient = class {
|
|
23
|
+
async request(method, url, options = {}) {
|
|
24
|
+
const { headers = {}, body, timeout } = options;
|
|
25
|
+
let signal = options.signal;
|
|
26
|
+
let timeoutId;
|
|
27
|
+
if (timeout && !signal) {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
signal = controller.signal;
|
|
30
|
+
timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
31
|
+
}
|
|
32
|
+
const isFormData = body instanceof FormData;
|
|
33
|
+
const fetchInit = {
|
|
34
|
+
method,
|
|
35
|
+
signal,
|
|
36
|
+
headers: isFormData ? headers : { "Content-Type": "application/json", ...headers },
|
|
37
|
+
body: body === void 0 || body === null ? void 0 : isFormData ? body : JSON.stringify(body)
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch(url, fetchInit);
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
let errorBody;
|
|
43
|
+
try {
|
|
44
|
+
errorBody = await res.json();
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
throw new JoopHttpError(res.status, res.statusText, errorBody);
|
|
48
|
+
}
|
|
49
|
+
const text = await res.text();
|
|
50
|
+
return text ? JSON.parse(text) : void 0;
|
|
51
|
+
} finally {
|
|
52
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
get(url, options) {
|
|
56
|
+
return this.request("GET" /* Get */, url, options);
|
|
57
|
+
}
|
|
58
|
+
post(url, body, options) {
|
|
59
|
+
return this.request("POST" /* Post */, url, { ...options, body });
|
|
60
|
+
}
|
|
61
|
+
put(url, body, options) {
|
|
62
|
+
return this.request("PUT" /* Put */, url, { ...options, body });
|
|
63
|
+
}
|
|
64
|
+
patch(url, body, options) {
|
|
65
|
+
return this.request("PATCH" /* Patch */, url, { ...options, body });
|
|
66
|
+
}
|
|
67
|
+
delete(url, options) {
|
|
68
|
+
return this.request("DELETE" /* Delete */, url, options);
|
|
69
|
+
}
|
|
70
|
+
/** Upload files with progress via XMLHttpRequest (fetch does not expose upload progress). */
|
|
71
|
+
uploadWithProgress(url, formData, onProgress, headers = {}) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const xhr = new XMLHttpRequest();
|
|
74
|
+
xhr.open("POST", url);
|
|
75
|
+
Object.entries(headers).forEach(([k, v]) => xhr.setRequestHeader(k, v));
|
|
76
|
+
xhr.upload.onprogress = (e) => {
|
|
77
|
+
if (e.lengthComputable) onProgress(Math.round(e.loaded / e.total * 100));
|
|
78
|
+
};
|
|
79
|
+
xhr.onload = () => {
|
|
80
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
81
|
+
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : void 0);
|
|
82
|
+
} else {
|
|
83
|
+
reject(new JoopHttpError(xhr.status, xhr.statusText));
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
xhr.onerror = () => reject(new JoopHttpError(0, "Network error"));
|
|
87
|
+
xhr.send(formData);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/events/index.ts
|
|
93
|
+
var JoopSubject = class {
|
|
94
|
+
_listeners = [];
|
|
95
|
+
subscribe(listener) {
|
|
96
|
+
this._listeners.push(listener);
|
|
97
|
+
return () => {
|
|
98
|
+
this._listeners = this._listeners.filter((l) => l !== listener);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
next(value) {
|
|
102
|
+
for (const listener of this._listeners) {
|
|
103
|
+
listener(value);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
asObservable() {
|
|
107
|
+
return new JoopObservable((listener) => this.subscribe(listener));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var JoopBehaviorSubject = class extends JoopSubject {
|
|
111
|
+
_value;
|
|
112
|
+
constructor(initialValue) {
|
|
113
|
+
super();
|
|
114
|
+
this._value = initialValue;
|
|
115
|
+
}
|
|
116
|
+
getValue() {
|
|
117
|
+
return this._value;
|
|
118
|
+
}
|
|
119
|
+
next(value) {
|
|
120
|
+
this._value = value;
|
|
121
|
+
super.next(value);
|
|
122
|
+
}
|
|
123
|
+
subscribe(listener) {
|
|
124
|
+
listener(this._value);
|
|
125
|
+
return super.subscribe(listener);
|
|
126
|
+
}
|
|
127
|
+
asObservable() {
|
|
128
|
+
return new JoopObservable((listener) => this.subscribe(listener));
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
var JoopObservable = class {
|
|
132
|
+
constructor(_subscribeFn) {
|
|
133
|
+
this._subscribeFn = _subscribeFn;
|
|
134
|
+
}
|
|
135
|
+
_subscribeFn;
|
|
136
|
+
subscribe(listener) {
|
|
137
|
+
return this._subscribeFn(listener);
|
|
138
|
+
}
|
|
139
|
+
/** Returns the current value without subscribing (only meaningful for BehaviorSubject-backed observables). */
|
|
140
|
+
getOnce() {
|
|
141
|
+
let result;
|
|
142
|
+
const unsub = this.subscribe((v) => {
|
|
143
|
+
result = v;
|
|
144
|
+
});
|
|
145
|
+
unsub();
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/constants/index.ts
|
|
151
|
+
var DECRYPTION_FAILED = "Response decryption failed";
|
|
152
|
+
|
|
153
|
+
// src/utilities/common.utility.ts
|
|
154
|
+
function removeTrailingSlash(url) {
|
|
155
|
+
return url?.endsWith("/") ? url.slice(0, -1) : url;
|
|
156
|
+
}
|
|
157
|
+
function objectToQueryString(obj) {
|
|
158
|
+
return Object.entries(obj).filter(([, v]) => v !== void 0 && v !== null).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/api/request.service.ts
|
|
162
|
+
var JoopRequestService = class {
|
|
163
|
+
constructor(_http, _config, _profile, _aes) {
|
|
164
|
+
this._http = _http;
|
|
165
|
+
this._config = _config;
|
|
166
|
+
this._profile = _profile;
|
|
167
|
+
this._aes = _aes;
|
|
168
|
+
}
|
|
169
|
+
_http;
|
|
170
|
+
_config;
|
|
171
|
+
_profile;
|
|
172
|
+
_aes;
|
|
173
|
+
_loading$ = new JoopBehaviorSubject({ active: false, count: 0 });
|
|
174
|
+
_requestCount = 0;
|
|
175
|
+
/** Observable loading state — subscribe in any framework to drive a spinner. */
|
|
176
|
+
loading$() {
|
|
177
|
+
return this._loading$.asObservable();
|
|
178
|
+
}
|
|
179
|
+
isLoading() {
|
|
180
|
+
return this._loading$.getValue().active;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Send a service request to the configured base URL.
|
|
184
|
+
* @param serviceId Service/endpoint identifier appended to the base URL
|
|
185
|
+
* @param data Request payload
|
|
186
|
+
* @param options Loader, error, method overrides
|
|
187
|
+
*/
|
|
188
|
+
async send(serviceId, data = {}, options = {}) {
|
|
189
|
+
const { useLoader = true, method = "POST" /* Post */, headers = {} } = options;
|
|
190
|
+
if (useLoader) this._incrementLoader();
|
|
191
|
+
try {
|
|
192
|
+
const url = this._buildUrl(serviceId);
|
|
193
|
+
const body = await this._prepareBody(data);
|
|
194
|
+
const fetchOpts = {
|
|
195
|
+
headers,
|
|
196
|
+
timeout: this._config.requestTimeout
|
|
197
|
+
};
|
|
198
|
+
let response;
|
|
199
|
+
if (method === "GET" /* Get */) {
|
|
200
|
+
const qs = objectToQueryString(body);
|
|
201
|
+
response = await this._http.get(`${url}?${qs}`, fetchOpts);
|
|
202
|
+
} else {
|
|
203
|
+
response = await this._http.post(url, body, fetchOpts);
|
|
204
|
+
}
|
|
205
|
+
return await this._handleResponse(response);
|
|
206
|
+
} finally {
|
|
207
|
+
if (useLoader) this._decrementLoader();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Upload a file with progress. */
|
|
211
|
+
async upload(serviceId, formData, onProgress) {
|
|
212
|
+
const url = this._buildUrl(serviceId);
|
|
213
|
+
this._incrementLoader();
|
|
214
|
+
try {
|
|
215
|
+
const res = await this._http.uploadWithProgress(url, formData, onProgress);
|
|
216
|
+
return await this._handleResponse(res);
|
|
217
|
+
} finally {
|
|
218
|
+
this._decrementLoader();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
_buildUrl(serviceId) {
|
|
222
|
+
const base = removeTrailingSlash(this._config.baseUrl);
|
|
223
|
+
return `${base}/${serviceId}`;
|
|
224
|
+
}
|
|
225
|
+
async _prepareBody(data) {
|
|
226
|
+
if (!this._aes.isE2EEnabled(this._profile.isInitialRequest)) {
|
|
227
|
+
return data;
|
|
228
|
+
}
|
|
229
|
+
const serialized = objectToQueryString(data);
|
|
230
|
+
const encrypted = await this._aes.processEncryption(serialized, this._profile.pageToken);
|
|
231
|
+
return encrypted;
|
|
232
|
+
}
|
|
233
|
+
async _handleResponse(response) {
|
|
234
|
+
if (response?.encrypted && response?.encryptedData) {
|
|
235
|
+
try {
|
|
236
|
+
const decrypted = await this._aes.decryptResponse(response.encryptedData);
|
|
237
|
+
return JSON.parse(decrypted);
|
|
238
|
+
} catch {
|
|
239
|
+
throw new JoopEncryptionError(DECRYPTION_FAILED);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return response;
|
|
243
|
+
}
|
|
244
|
+
_incrementLoader() {
|
|
245
|
+
this._requestCount++;
|
|
246
|
+
this._loading$.next({ active: true, count: this._requestCount });
|
|
247
|
+
}
|
|
248
|
+
_decrementLoader() {
|
|
249
|
+
this._requestCount = Math.max(0, this._requestCount - 1);
|
|
250
|
+
this._loading$.next({ active: this._requestCount > 0, count: this._requestCount });
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
function isHttpError(err, status) {
|
|
254
|
+
return err instanceof JoopHttpError && (status === void 0 || err.statusCode === status);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/api/http/interceptor.ts
|
|
258
|
+
var JoopInterceptorPipeline = class {
|
|
259
|
+
_req = [];
|
|
260
|
+
_res = [];
|
|
261
|
+
_err = [];
|
|
262
|
+
addRequestInterceptor(fn) {
|
|
263
|
+
this._req.push(fn);
|
|
264
|
+
return () => {
|
|
265
|
+
this._req = this._req.filter((i) => i !== fn);
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
addResponseInterceptor(fn) {
|
|
269
|
+
this._res.push(fn);
|
|
270
|
+
return () => {
|
|
271
|
+
this._res = this._res.filter((i) => i !== fn);
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
addErrorInterceptor(fn) {
|
|
275
|
+
this._err.push(fn);
|
|
276
|
+
return () => {
|
|
277
|
+
this._err = this._err.filter((i) => i !== fn);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
async processRequest(req) {
|
|
281
|
+
let cur = req;
|
|
282
|
+
for (const fn of this._req) cur = await fn(cur);
|
|
283
|
+
return cur;
|
|
284
|
+
}
|
|
285
|
+
async processResponse(res) {
|
|
286
|
+
let cur = res;
|
|
287
|
+
for (const fn of this._res) cur = await fn(cur);
|
|
288
|
+
return cur;
|
|
289
|
+
}
|
|
290
|
+
async processError(err) {
|
|
291
|
+
let cur = err;
|
|
292
|
+
for (const fn of this._err) cur = await fn(cur);
|
|
293
|
+
return cur;
|
|
294
|
+
}
|
|
295
|
+
clear() {
|
|
296
|
+
this._req = [];
|
|
297
|
+
this._res = [];
|
|
298
|
+
this._err = [];
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/api/http/retry.ts
|
|
303
|
+
var DEFAULTS = {
|
|
304
|
+
maxAttempts: 3,
|
|
305
|
+
baseDelayMs: 500,
|
|
306
|
+
maxDelayMs: 3e4,
|
|
307
|
+
jitter: true
|
|
308
|
+
};
|
|
309
|
+
async function withRetry(fn, config = {}) {
|
|
310
|
+
const cfg = { ...DEFAULTS, ...config };
|
|
311
|
+
let lastError;
|
|
312
|
+
for (let attempt = 1; attempt <= cfg.maxAttempts; attempt++) {
|
|
313
|
+
try {
|
|
314
|
+
return await fn();
|
|
315
|
+
} catch (err) {
|
|
316
|
+
lastError = err;
|
|
317
|
+
if (attempt === cfg.maxAttempts) break;
|
|
318
|
+
if (cfg.retryOn && !cfg.retryOn(err, attempt)) break;
|
|
319
|
+
await _sleep(_calcDelay(attempt, cfg));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
throw lastError;
|
|
323
|
+
}
|
|
324
|
+
function _calcDelay(attempt, cfg) {
|
|
325
|
+
const exp = Math.min(cfg.baseDelayMs * Math.pow(2, attempt - 1), cfg.maxDelayMs);
|
|
326
|
+
return cfg.jitter ? Math.floor(Math.random() * exp) : exp;
|
|
327
|
+
}
|
|
328
|
+
function _sleep(ms) {
|
|
329
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/api/http/circuit-breaker.ts
|
|
333
|
+
var JoopCircuitBreakerOpenError = class extends Error {
|
|
334
|
+
nextAttemptAt;
|
|
335
|
+
constructor(name, nextAttemptAt) {
|
|
336
|
+
super(`Circuit "${name}" is OPEN. Retry after ${new Date(nextAttemptAt).toISOString()}`);
|
|
337
|
+
this.name = "JoopCircuitBreakerOpenError";
|
|
338
|
+
this.nextAttemptAt = nextAttemptAt;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
var JoopCircuitBreaker = class {
|
|
342
|
+
_name;
|
|
343
|
+
failureThreshold;
|
|
344
|
+
successThreshold;
|
|
345
|
+
timeoutMs;
|
|
346
|
+
volumeThreshold;
|
|
347
|
+
onStateChangeFn;
|
|
348
|
+
_state = "closed";
|
|
349
|
+
_failures = 0;
|
|
350
|
+
_successes = 0;
|
|
351
|
+
_totalCalls = 0;
|
|
352
|
+
_lastFailureAt;
|
|
353
|
+
_openedAt;
|
|
354
|
+
constructor(nameOrConfig, config = {}) {
|
|
355
|
+
const merged = typeof nameOrConfig === "string" ? { name: nameOrConfig, ...config } : { ...nameOrConfig, ...config };
|
|
356
|
+
this._name = merged.name ?? "default";
|
|
357
|
+
this.failureThreshold = merged.failureThreshold ?? 5;
|
|
358
|
+
this.successThreshold = merged.successThreshold ?? 2;
|
|
359
|
+
this.timeoutMs = merged.timeoutMs ?? 3e4;
|
|
360
|
+
this.volumeThreshold = merged.volumeThreshold ?? 1;
|
|
361
|
+
this.onStateChangeFn = merged.onStateChange;
|
|
362
|
+
}
|
|
363
|
+
get state() {
|
|
364
|
+
return this._state;
|
|
365
|
+
}
|
|
366
|
+
get name() {
|
|
367
|
+
return this._name;
|
|
368
|
+
}
|
|
369
|
+
setState(to) {
|
|
370
|
+
if (to === this._state) return;
|
|
371
|
+
const from = this._state;
|
|
372
|
+
this._state = to;
|
|
373
|
+
if (to === "open") this._openedAt = Date.now();
|
|
374
|
+
this.onStateChangeFn?.(from, to, this._name);
|
|
375
|
+
}
|
|
376
|
+
recordSuccess() {
|
|
377
|
+
this._successes++;
|
|
378
|
+
this._failures = 0;
|
|
379
|
+
if (this._state === "half-open" && this._successes >= this.successThreshold) {
|
|
380
|
+
this._successes = 0;
|
|
381
|
+
this.setState("closed");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
recordFailure() {
|
|
385
|
+
this._failures++;
|
|
386
|
+
this._lastFailureAt = Date.now();
|
|
387
|
+
this._successes = 0;
|
|
388
|
+
if (this._state === "half-open") {
|
|
389
|
+
this.setState("open");
|
|
390
|
+
} else if (this._state === "closed" && this._totalCalls >= this.volumeThreshold && this._failures >= this.failureThreshold) {
|
|
391
|
+
this.setState("open");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
canAttempt() {
|
|
395
|
+
if (this._state !== "open") return true;
|
|
396
|
+
if (Date.now() - (this._openedAt ?? 0) >= this.timeoutMs) {
|
|
397
|
+
this.setState("half-open");
|
|
398
|
+
this._successes = 0;
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
/** Primary execution method — throws JoopCircuitBreakerOpenError when open. */
|
|
404
|
+
async execute(fn) {
|
|
405
|
+
if (!this.canAttempt()) {
|
|
406
|
+
throw new JoopCircuitBreakerOpenError(this._name, (this._openedAt ?? 0) + this.timeoutMs);
|
|
407
|
+
}
|
|
408
|
+
this._totalCalls++;
|
|
409
|
+
try {
|
|
410
|
+
const result = await fn();
|
|
411
|
+
this.recordSuccess();
|
|
412
|
+
return result;
|
|
413
|
+
} catch (err) {
|
|
414
|
+
this.recordFailure();
|
|
415
|
+
throw err;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/** Alias kept for back-compat with original `call()` interface. */
|
|
419
|
+
async call(fn) {
|
|
420
|
+
return this.execute(fn);
|
|
421
|
+
}
|
|
422
|
+
async executeWithFallback(fn, fallback) {
|
|
423
|
+
try {
|
|
424
|
+
return await this.execute(fn);
|
|
425
|
+
} catch {
|
|
426
|
+
return fallback();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
getState() {
|
|
430
|
+
return this._state;
|
|
431
|
+
}
|
|
432
|
+
getStats() {
|
|
433
|
+
return {
|
|
434
|
+
state: this._state,
|
|
435
|
+
failures: this._failures,
|
|
436
|
+
successes: this._successes,
|
|
437
|
+
totalCalls: this._totalCalls,
|
|
438
|
+
lastFailureAt: this._lastFailureAt,
|
|
439
|
+
openedAt: this._openedAt,
|
|
440
|
+
nextAttemptAt: this._openedAt ? this._openedAt + this.timeoutMs : void 0
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
reset() {
|
|
444
|
+
this._state = "closed";
|
|
445
|
+
this._failures = 0;
|
|
446
|
+
this._successes = 0;
|
|
447
|
+
this._totalCalls = 0;
|
|
448
|
+
this._lastFailureAt = void 0;
|
|
449
|
+
this._openedAt = void 0;
|
|
450
|
+
}
|
|
451
|
+
forceOpen() {
|
|
452
|
+
this.setState("open");
|
|
453
|
+
}
|
|
454
|
+
forceClose() {
|
|
455
|
+
this._failures = 0;
|
|
456
|
+
this.setState("closed");
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// src/api/http/offline-queue.service.ts
|
|
461
|
+
var JoopOfflineQueue = class {
|
|
462
|
+
_queue = [];
|
|
463
|
+
_online = typeof navigator !== "undefined" ? navigator.onLine : true;
|
|
464
|
+
_draining = false;
|
|
465
|
+
constructor() {
|
|
466
|
+
if (typeof window !== "undefined") {
|
|
467
|
+
window.addEventListener("online", () => this._onOnline());
|
|
468
|
+
window.addEventListener("offline", () => {
|
|
469
|
+
this._online = false;
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
isOnline() {
|
|
474
|
+
return this._online;
|
|
475
|
+
}
|
|
476
|
+
queueLength() {
|
|
477
|
+
return this._queue.length;
|
|
478
|
+
}
|
|
479
|
+
enqueue(fn) {
|
|
480
|
+
return new Promise((resolve, reject) => {
|
|
481
|
+
this._queue.push({
|
|
482
|
+
id: crypto.randomUUID(),
|
|
483
|
+
fn,
|
|
484
|
+
resolve,
|
|
485
|
+
reject,
|
|
486
|
+
timestamp: Date.now()
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
async drainNow() {
|
|
491
|
+
if (this._draining) return;
|
|
492
|
+
this._draining = true;
|
|
493
|
+
while (this._queue.length > 0) {
|
|
494
|
+
const entry = this._queue.shift();
|
|
495
|
+
try {
|
|
496
|
+
entry.resolve(await entry.fn());
|
|
497
|
+
} catch (err) {
|
|
498
|
+
entry.reject(err);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
this._draining = false;
|
|
502
|
+
}
|
|
503
|
+
clear() {
|
|
504
|
+
this._queue.forEach((e) => e.reject(new Error("Queue cleared")));
|
|
505
|
+
this._queue = [];
|
|
506
|
+
}
|
|
507
|
+
_onOnline() {
|
|
508
|
+
this._online = true;
|
|
509
|
+
this.drainNow().catch(() => {
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// src/api/http/polling.service.ts
|
|
515
|
+
var JoopPollingService = class {
|
|
516
|
+
_timers = /* @__PURE__ */ new Map();
|
|
517
|
+
_subjects = /* @__PURE__ */ new Map();
|
|
518
|
+
start(id, config) {
|
|
519
|
+
this.stop(id);
|
|
520
|
+
const subject = new JoopSubject();
|
|
521
|
+
this._subjects.set(id, subject);
|
|
522
|
+
const run = async () => {
|
|
523
|
+
try {
|
|
524
|
+
const result = await config.fn();
|
|
525
|
+
subject.next(result);
|
|
526
|
+
if (config.stopWhen?.(result)) this.stop(id);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
config.onError?.(err);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
if (config.immediate) run();
|
|
532
|
+
this._timers.set(id, setInterval(run, config.intervalMs));
|
|
533
|
+
return subject;
|
|
534
|
+
}
|
|
535
|
+
stop(id) {
|
|
536
|
+
const timer = this._timers.get(id);
|
|
537
|
+
if (timer) {
|
|
538
|
+
clearInterval(timer);
|
|
539
|
+
this._timers.delete(id);
|
|
540
|
+
}
|
|
541
|
+
this._subjects.delete(id);
|
|
542
|
+
}
|
|
543
|
+
stopAll() {
|
|
544
|
+
[...this._timers.keys()].forEach((id) => this.stop(id));
|
|
545
|
+
}
|
|
546
|
+
isPolling(id) {
|
|
547
|
+
return this._timers.has(id);
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// src/api/http/file-saver.service.ts
|
|
552
|
+
var IOS_WEBVIEW_MIME = {
|
|
553
|
+
xls: "data:application/xls;base64,",
|
|
554
|
+
csv: "data:text/xml;base64,",
|
|
555
|
+
pdf: "data:application/pdf;base64,",
|
|
556
|
+
xlsx: "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,",
|
|
557
|
+
doc: "data:application/msword;base64,",
|
|
558
|
+
docx: "data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,"
|
|
559
|
+
};
|
|
560
|
+
var MIME_TYPES = {
|
|
561
|
+
txt: "text/plain",
|
|
562
|
+
pdf: "application/pdf",
|
|
563
|
+
csv: "text/csv",
|
|
564
|
+
xls: "application/vnd.ms-excel",
|
|
565
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
566
|
+
doc: "application/msword",
|
|
567
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
568
|
+
png: "image/png",
|
|
569
|
+
jpg: "image/jpeg",
|
|
570
|
+
jpeg: "image/jpeg",
|
|
571
|
+
gif: "image/gif"
|
|
572
|
+
};
|
|
573
|
+
var JoopFileSaverService = class {
|
|
574
|
+
isIosWebView(handlerName = "mxDownloadPdf") {
|
|
575
|
+
return !!window?.["webkit"]?.["messageHandlers"]?.[handlerName];
|
|
576
|
+
}
|
|
577
|
+
async download(url, options = {}) {
|
|
578
|
+
const res = await fetch(url, {
|
|
579
|
+
method: options.method ?? "GET",
|
|
580
|
+
headers: options.headers,
|
|
581
|
+
body: options.body
|
|
582
|
+
});
|
|
583
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
584
|
+
const blob = await res.blob();
|
|
585
|
+
const fileName = this._fileNameFromHeaders(res.headers) ?? "download";
|
|
586
|
+
if (options.iosHandlerName && this.isIosWebView(options.iosHandlerName)) {
|
|
587
|
+
await this._sendToIos(blob, fileName, options.iosHandlerName);
|
|
588
|
+
} else {
|
|
589
|
+
this.saveBlob(blob, fileName);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
saveBlob(blob, fileName) {
|
|
593
|
+
const url = URL.createObjectURL(blob);
|
|
594
|
+
const a = document.createElement("a");
|
|
595
|
+
a.href = url;
|
|
596
|
+
a.download = fileName;
|
|
597
|
+
a.style.display = "none";
|
|
598
|
+
document.body.appendChild(a);
|
|
599
|
+
a.click();
|
|
600
|
+
document.body.removeChild(a);
|
|
601
|
+
setTimeout(() => URL.revokeObjectURL(url), 5e3);
|
|
602
|
+
}
|
|
603
|
+
blobToBase64(blob) {
|
|
604
|
+
return new Promise((resolve, reject) => {
|
|
605
|
+
const reader = new FileReader();
|
|
606
|
+
reader.onload = () => resolve(reader.result);
|
|
607
|
+
reader.onerror = reject;
|
|
608
|
+
reader.readAsDataURL(blob);
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
getMimeType(fileName) {
|
|
612
|
+
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
613
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
614
|
+
}
|
|
615
|
+
typedBlob(blob, fileName) {
|
|
616
|
+
return new Blob([blob], { type: this.getMimeType(fileName) });
|
|
617
|
+
}
|
|
618
|
+
async _sendToIos(blob, fileName, handlerName) {
|
|
619
|
+
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
620
|
+
const prefix = IOS_WEBVIEW_MIME[ext] ?? "";
|
|
621
|
+
const base64 = await this.blobToBase64(blob);
|
|
622
|
+
const parts = base64.split("base64,");
|
|
623
|
+
const payload = prefix + (parts.length > 1 ? parts[1] : parts[0]);
|
|
624
|
+
window["webkit"]["messageHandlers"][handlerName].postMessage(payload);
|
|
625
|
+
}
|
|
626
|
+
_fileNameFromHeaders(headers) {
|
|
627
|
+
const cd = headers.get("Content-Disposition") ?? "";
|
|
628
|
+
const match = /filename=([^;]+)/i.exec(cd);
|
|
629
|
+
return match ? match[1].trim().replace(/['"]/g, "") : null;
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
// src/api/response-mapper.ts
|
|
634
|
+
var SCOPE_PREFIXES = {
|
|
635
|
+
M: "M",
|
|
636
|
+
O: "O",
|
|
637
|
+
N: "N",
|
|
638
|
+
S: "S"
|
|
639
|
+
};
|
|
640
|
+
var JoopResponseMapper = class {
|
|
641
|
+
/** Normalize a v6 or v7+ raw API response into a typed mapped response */
|
|
642
|
+
map(raw) {
|
|
643
|
+
const v7 = this._isV7(raw) ? raw : this._convertV6ToV7(raw);
|
|
644
|
+
const components = v7.components ?? {};
|
|
645
|
+
const componentList = Object.entries(components).map(([key, value]) => ({
|
|
646
|
+
key,
|
|
647
|
+
value,
|
|
648
|
+
scope: this.extractScope(key),
|
|
649
|
+
dataKey: this.extractDataKey(key)
|
|
650
|
+
}));
|
|
651
|
+
return {
|
|
652
|
+
scrid: v7.scrid ?? "",
|
|
653
|
+
segments: v7.segments ?? "",
|
|
654
|
+
mxenc: v7.mxenc ?? "",
|
|
655
|
+
mxencres: v7.mxencres ?? "",
|
|
656
|
+
components,
|
|
657
|
+
componentList,
|
|
658
|
+
errorCode: v7.errorCode,
|
|
659
|
+
errorDesc: v7.errorDesc,
|
|
660
|
+
hasError: !!(v7.errorCode && v7.errorCode !== "0" && v7.errorCode !== "")
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
/** Extract scope prefix from a component key (first char: M/O/N/S) */
|
|
664
|
+
extractScope(key) {
|
|
665
|
+
const first = key.charAt(0).toUpperCase();
|
|
666
|
+
return SCOPE_PREFIXES[first] ?? "M";
|
|
667
|
+
}
|
|
668
|
+
/** Extract data key portion (everything after the first character) */
|
|
669
|
+
extractDataKey(key) {
|
|
670
|
+
return key.length > 1 ? key.slice(1) : key;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Build a property map from component list — groups by scope prefix.
|
|
674
|
+
* e.g. "MUserName" → { scope: 'M', dataKey: 'UserName', value: '...' }
|
|
675
|
+
*/
|
|
676
|
+
buildScopedMap(response) {
|
|
677
|
+
const result = {};
|
|
678
|
+
for (const comp of response.componentList) {
|
|
679
|
+
if (!result[comp.scope]) result[comp.scope] = {};
|
|
680
|
+
result[comp.scope][comp.dataKey] = comp.value;
|
|
681
|
+
}
|
|
682
|
+
return result;
|
|
683
|
+
}
|
|
684
|
+
/** Get a single component value from a mapped response */
|
|
685
|
+
getComponent(response, key) {
|
|
686
|
+
return response.components[key] ?? null;
|
|
687
|
+
}
|
|
688
|
+
/** Check if the raw payload looks like a v7+ response (has 'components' key) */
|
|
689
|
+
_isV7(raw) {
|
|
690
|
+
return "components" in raw && raw.components != null;
|
|
691
|
+
}
|
|
692
|
+
_convertV6ToV7(raw) {
|
|
693
|
+
const components = {};
|
|
694
|
+
for (const item of raw.compDet ?? []) {
|
|
695
|
+
if (item.compKey) components[item.compKey] = item.compValue ?? "";
|
|
696
|
+
}
|
|
697
|
+
return {
|
|
698
|
+
scrid: raw.scrid ?? raw.scrId,
|
|
699
|
+
segments: raw.segments,
|
|
700
|
+
mxenc: raw.mxenc,
|
|
701
|
+
mxencres: raw.mxencres,
|
|
702
|
+
components,
|
|
703
|
+
errorCode: raw.errorCode,
|
|
704
|
+
errorDesc: raw.errorDesc
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// src/api/export.service.ts
|
|
710
|
+
var JoopExportService = class {
|
|
711
|
+
/** Convert an array of objects to a CSV string */
|
|
712
|
+
toCsv(data, options = {}) {
|
|
713
|
+
if (!data.length) return "";
|
|
714
|
+
const {
|
|
715
|
+
delimiter = ",",
|
|
716
|
+
headers,
|
|
717
|
+
includeHeaders = true,
|
|
718
|
+
bom = false,
|
|
719
|
+
nullValue = ""
|
|
720
|
+
} = options;
|
|
721
|
+
const cols = headers ?? Object.keys(data[0]);
|
|
722
|
+
const escape = (v) => {
|
|
723
|
+
const s = v == null ? nullValue : String(v);
|
|
724
|
+
return s.includes(delimiter) || s.includes('"') || s.includes("\n") ? `"${s.replace(/"/g, '""')}"` : s;
|
|
725
|
+
};
|
|
726
|
+
const rows = [];
|
|
727
|
+
if (includeHeaders) rows.push(cols.map(escape).join(delimiter));
|
|
728
|
+
for (const row of data) rows.push(cols.map((k) => escape(row[k])).join(delimiter));
|
|
729
|
+
const csv = rows.join("\r\n");
|
|
730
|
+
return bom ? "\uFEFF" + csv : csv;
|
|
731
|
+
}
|
|
732
|
+
/** Trigger browser download of a CSV string */
|
|
733
|
+
downloadCsv(data, filename, options = {}) {
|
|
734
|
+
const csv = this.toCsv(data, options);
|
|
735
|
+
this._download(csv, filename.endsWith(".csv") ? filename : `${filename}.csv`, "text/csv;charset=utf-8;");
|
|
736
|
+
}
|
|
737
|
+
/** Download Excel-compatible CSV (with BOM so Excel reads UTF-8 correctly) */
|
|
738
|
+
downloadExcel(data, filename, options = {}) {
|
|
739
|
+
const csv = this.toCsv(data, { ...options, bom: true, delimiter: "," });
|
|
740
|
+
this._download(csv, filename.endsWith(".csv") ? filename : `${filename}.csv`, "text/csv;charset=utf-8;");
|
|
741
|
+
}
|
|
742
|
+
/** Convert data to a JSON string and download */
|
|
743
|
+
downloadJson(data, filename, indent = 2) {
|
|
744
|
+
const json = JSON.stringify(data, null, indent);
|
|
745
|
+
this._download(json, filename.endsWith(".json") ? filename : `${filename}.json`, "application/json");
|
|
746
|
+
}
|
|
747
|
+
/** Download a plain-text file */
|
|
748
|
+
downloadText(content, filename) {
|
|
749
|
+
this._download(content, filename, "text/plain;charset=utf-8;");
|
|
750
|
+
}
|
|
751
|
+
/** Convert a 2D array (table) to CSV */
|
|
752
|
+
tableToCsv(rows, headers, delimiter = ",") {
|
|
753
|
+
const esc = (v) => {
|
|
754
|
+
const s = v == null ? "" : String(v);
|
|
755
|
+
return s.includes(delimiter) || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s;
|
|
756
|
+
};
|
|
757
|
+
const lines = [];
|
|
758
|
+
if (headers) lines.push(headers.map(esc).join(delimiter));
|
|
759
|
+
for (const row of rows) lines.push(row.map(esc).join(delimiter));
|
|
760
|
+
return lines.join("\r\n");
|
|
761
|
+
}
|
|
762
|
+
_download(content, filename, mimeType) {
|
|
763
|
+
const blob = new Blob([content], { type: mimeType });
|
|
764
|
+
const url = URL.createObjectURL(blob);
|
|
765
|
+
const a = document.createElement("a");
|
|
766
|
+
a.href = url;
|
|
767
|
+
a.download = filename;
|
|
768
|
+
a.style.display = "none";
|
|
769
|
+
document.body.appendChild(a);
|
|
770
|
+
a.click();
|
|
771
|
+
document.body.removeChild(a);
|
|
772
|
+
setTimeout(() => URL.revokeObjectURL(url), 5e3);
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
// src/api/webhook-verifier.ts
|
|
777
|
+
var JoopWebhookVerifier = class _JoopWebhookVerifier {
|
|
778
|
+
_provider = "generic";
|
|
779
|
+
for(provider) {
|
|
780
|
+
const clone = new _JoopWebhookVerifier();
|
|
781
|
+
clone._provider = provider;
|
|
782
|
+
return clone;
|
|
783
|
+
}
|
|
784
|
+
async verify(payload, signature, secret, opts = {}) {
|
|
785
|
+
switch (this._provider) {
|
|
786
|
+
case "stripe":
|
|
787
|
+
return this.verifyStripe(payload, signature, secret, opts);
|
|
788
|
+
case "github":
|
|
789
|
+
return this.verifyGitHub(payload, signature, secret, opts);
|
|
790
|
+
default:
|
|
791
|
+
return this.verifyGeneric(payload, signature, secret, opts);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
/** Stripe: `Stripe-Signature: t=...,v1=...` */
|
|
795
|
+
async verifyStripe(payload, header, secret, opts = {}) {
|
|
796
|
+
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
|
|
797
|
+
const t = parts["t"];
|
|
798
|
+
const v1 = parts["v1"];
|
|
799
|
+
if (!t || !v1) return false;
|
|
800
|
+
if (!this.verifyTimestamp(parseInt(t, 10), opts.toleranceSeconds ?? 300)) return false;
|
|
801
|
+
const signed = `${t}.${payload}`;
|
|
802
|
+
const expected = await _hmac("SHA-256", secret, signed);
|
|
803
|
+
return _safeEqual(expected, v1);
|
|
804
|
+
}
|
|
805
|
+
/** GitHub: `X-Hub-Signature-256: sha256=...` */
|
|
806
|
+
async verifyGitHub(payload, header, secret, opts = {}) {
|
|
807
|
+
const prefix = "sha256=";
|
|
808
|
+
if (!header.startsWith(prefix)) return false;
|
|
809
|
+
const provided = header.slice(prefix.length);
|
|
810
|
+
const expected = await _hmac("SHA-256", secret, payload);
|
|
811
|
+
return _safeEqual(expected, provided);
|
|
812
|
+
}
|
|
813
|
+
/** Generic HMAC verification. signature is a hex string. */
|
|
814
|
+
async verifyGeneric(payload, signature, secret, opts = {}) {
|
|
815
|
+
const algo = opts.algorithm ?? "SHA-256";
|
|
816
|
+
const expected = await _hmac(algo, secret, payload);
|
|
817
|
+
return _safeEqual(expected, signature);
|
|
818
|
+
}
|
|
819
|
+
/** Returns true if the timestamp is within tolerance of now */
|
|
820
|
+
verifyTimestamp(timestamp, toleranceSeconds = 300) {
|
|
821
|
+
const diff = Math.abs(Math.floor(Date.now() / 1e3) - timestamp);
|
|
822
|
+
return diff <= toleranceSeconds;
|
|
823
|
+
}
|
|
824
|
+
/** Compute the expected HMAC for a payload (for testing) */
|
|
825
|
+
async sign(payload, secret, algo = "SHA-256") {
|
|
826
|
+
return _hmac(algo, secret, payload);
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
async function _hmac(algorithm, secret, payload) {
|
|
830
|
+
const enc = new TextEncoder();
|
|
831
|
+
const key = await crypto.subtle.importKey(
|
|
832
|
+
"raw",
|
|
833
|
+
enc.encode(secret),
|
|
834
|
+
{ name: "HMAC", hash: algorithm },
|
|
835
|
+
false,
|
|
836
|
+
["sign"]
|
|
837
|
+
);
|
|
838
|
+
const sig = await crypto.subtle.sign("HMAC", key, enc.encode(payload));
|
|
839
|
+
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
840
|
+
}
|
|
841
|
+
function _safeEqual(a, b) {
|
|
842
|
+
if (a.length !== b.length) return false;
|
|
843
|
+
let diff = 0;
|
|
844
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
845
|
+
return diff === 0;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/platform/platform.ts
|
|
849
|
+
var _webStorage = (store) => ({
|
|
850
|
+
getItem: (k) => store.getItem(k),
|
|
851
|
+
setItem: (k, v) => store.setItem(k, v),
|
|
852
|
+
removeItem: (k) => store.removeItem(k),
|
|
853
|
+
clear: () => store.clear()
|
|
854
|
+
});
|
|
855
|
+
var _MemStorageImpl = class {
|
|
856
|
+
_m = /* @__PURE__ */ new Map();
|
|
857
|
+
getItem(k) {
|
|
858
|
+
return this._m.get(k) ?? null;
|
|
859
|
+
}
|
|
860
|
+
setItem(k, v) {
|
|
861
|
+
this._m.set(k, v);
|
|
862
|
+
}
|
|
863
|
+
removeItem(k) {
|
|
864
|
+
this._m.delete(k);
|
|
865
|
+
}
|
|
866
|
+
clear() {
|
|
867
|
+
this._m.clear();
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
function _detect() {
|
|
871
|
+
if (typeof navigator !== "undefined" && navigator.product === "ReactNative") return "react-native";
|
|
872
|
+
const _proc = globalThis["process"];
|
|
873
|
+
if (typeof window === "undefined" && _proc?.versions?.node) return "node";
|
|
874
|
+
if (typeof window !== "undefined") return "web";
|
|
875
|
+
return "unknown";
|
|
876
|
+
}
|
|
877
|
+
var JoopPlatform = class {
|
|
878
|
+
static _type = _detect();
|
|
879
|
+
static _adapter = {};
|
|
880
|
+
static _memStorage = null;
|
|
881
|
+
/**
|
|
882
|
+
* Call once at app startup to configure adapters for your platform.
|
|
883
|
+
*
|
|
884
|
+
* Web (default — no call needed):
|
|
885
|
+
* JoopPlatform.init();
|
|
886
|
+
*
|
|
887
|
+
* React Native with react-native-mmkv:
|
|
888
|
+
* import { MMKV } from 'react-native-mmkv';
|
|
889
|
+
* const mmkv = new MMKV();
|
|
890
|
+
* JoopPlatform.init({ adapter: { storage: mmkv } });
|
|
891
|
+
*
|
|
892
|
+
* React Native with custom compression (fflate):
|
|
893
|
+
* import { gzip, ungzip } from 'fflate';
|
|
894
|
+
* JoopPlatform.init({
|
|
895
|
+
* adapter: {
|
|
896
|
+
* compression: {
|
|
897
|
+
* compress: (d, fmt) => new Promise((res, rej) => gzip(d, (e, r) => e ? rej(e) : res(r))),
|
|
898
|
+
* decompress: (d, fmt) => new Promise((res, rej) => ungzip(d, (e, r) => e ? rej(e) : res(r))),
|
|
899
|
+
* },
|
|
900
|
+
* },
|
|
901
|
+
* });
|
|
902
|
+
*/
|
|
903
|
+
static init(config = {}) {
|
|
904
|
+
if (config.platform) this._type = config.platform;
|
|
905
|
+
this._adapter = { ...this._adapter, ...config.adapter ?? {} };
|
|
906
|
+
}
|
|
907
|
+
/** Current detected or overridden platform type */
|
|
908
|
+
static get type() {
|
|
909
|
+
return this._type;
|
|
910
|
+
}
|
|
911
|
+
static is(type) {
|
|
912
|
+
return this._type === type;
|
|
913
|
+
}
|
|
914
|
+
static isMobile() {
|
|
915
|
+
return this._type === "react-native";
|
|
916
|
+
}
|
|
917
|
+
static isWeb() {
|
|
918
|
+
return this._type === "web";
|
|
919
|
+
}
|
|
920
|
+
static isNode() {
|
|
921
|
+
return this._type === "node";
|
|
922
|
+
}
|
|
923
|
+
/** Raw adapter config registered via init() */
|
|
924
|
+
static getAdapter() {
|
|
925
|
+
return this._adapter;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Returns the best available storage adapter:
|
|
929
|
+
* 1. Custom adapter registered via init()
|
|
930
|
+
* 2. localStorage (web)
|
|
931
|
+
* 3. In-memory fallback (Node / RN without adapter)
|
|
932
|
+
*/
|
|
933
|
+
static getStorage() {
|
|
934
|
+
if (this._adapter.storage) return this._adapter.storage;
|
|
935
|
+
if (typeof localStorage !== "undefined") return _webStorage(localStorage);
|
|
936
|
+
if (!this._memStorage) this._memStorage = new _MemStorageImpl();
|
|
937
|
+
return this._memStorage;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Returns the best available session-scoped storage:
|
|
941
|
+
* 1. Custom adapter registered via init()
|
|
942
|
+
* 2. sessionStorage (web)
|
|
943
|
+
* 3. Shared in-memory fallback
|
|
944
|
+
*/
|
|
945
|
+
static getSessionStorage() {
|
|
946
|
+
if (this._adapter.storage) return this._adapter.storage;
|
|
947
|
+
if (typeof sessionStorage !== "undefined") return _webStorage(sessionStorage);
|
|
948
|
+
if (!this._memStorage) this._memStorage = new _MemStorageImpl();
|
|
949
|
+
return this._memStorage;
|
|
950
|
+
}
|
|
951
|
+
/** What APIs are available in the current environment */
|
|
952
|
+
static capabilities() {
|
|
953
|
+
return {
|
|
954
|
+
compression: typeof CompressionStream !== "undefined" || !!this._adapter.compression,
|
|
955
|
+
workers: typeof Worker !== "undefined" || !!this._adapter.worker,
|
|
956
|
+
cryptoSubtle: !!(typeof globalThis !== "undefined" && globalThis.crypto?.subtle),
|
|
957
|
+
persistentStorage: typeof localStorage !== "undefined" || !!this._adapter.storage,
|
|
958
|
+
dom: typeof document !== "undefined",
|
|
959
|
+
serviceWorker: typeof navigator !== "undefined" && "serviceWorker" in navigator
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
/** Reset to auto-detected defaults — useful between tests */
|
|
963
|
+
static _reset() {
|
|
964
|
+
this._type = _detect();
|
|
965
|
+
this._adapter = {};
|
|
966
|
+
this._memStorage = null;
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
// src/api/idempotency.service.ts
|
|
971
|
+
var JoopIdempotencyService = class {
|
|
972
|
+
_storage;
|
|
973
|
+
_ttlMs;
|
|
974
|
+
_prefix;
|
|
975
|
+
/** Internal key registry used for clearAll() and gc() (avoids requiring Storage.length/key()) */
|
|
976
|
+
_keys = /* @__PURE__ */ new Set();
|
|
977
|
+
constructor(config = {}) {
|
|
978
|
+
this._ttlMs = config.ttlMs ?? 24 * 60 * 60 * 1e3;
|
|
979
|
+
this._prefix = config.prefix ?? "joop_idem_";
|
|
980
|
+
this._storage = config.storage ?? JoopPlatform.getStorage();
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Generate and store an idempotency key for the given operation.
|
|
984
|
+
* Returns an existing (non-expired) key if one already exists.
|
|
985
|
+
*/
|
|
986
|
+
generate(operationKey) {
|
|
987
|
+
const storageKey = this._prefix + operationKey;
|
|
988
|
+
const existing = this.get(operationKey);
|
|
989
|
+
if (existing) {
|
|
990
|
+
this._keys.add(storageKey);
|
|
991
|
+
return existing;
|
|
992
|
+
}
|
|
993
|
+
const key = _generateKey();
|
|
994
|
+
const entry = { key, createdAt: Date.now(), expiresAt: Date.now() + this._ttlMs };
|
|
995
|
+
this._storage.setItem(storageKey, JSON.stringify(entry));
|
|
996
|
+
this._keys.add(storageKey);
|
|
997
|
+
return key;
|
|
998
|
+
}
|
|
999
|
+
/** Get the stored key for an operation, or null if expired/not found */
|
|
1000
|
+
get(operationKey) {
|
|
1001
|
+
const storageKey = this._prefix + operationKey;
|
|
1002
|
+
const raw = this._storage.getItem(storageKey);
|
|
1003
|
+
if (!raw) return null;
|
|
1004
|
+
try {
|
|
1005
|
+
const entry = JSON.parse(raw);
|
|
1006
|
+
if (Date.now() > entry.expiresAt) {
|
|
1007
|
+
this._storage.removeItem(storageKey);
|
|
1008
|
+
this._keys.delete(storageKey);
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
1011
|
+
return entry.key;
|
|
1012
|
+
} catch {
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
/** Remove the key for an operation (e.g. after successful completion) */
|
|
1017
|
+
clear(operationKey) {
|
|
1018
|
+
const storageKey = this._prefix + operationKey;
|
|
1019
|
+
this._storage.removeItem(storageKey);
|
|
1020
|
+
this._keys.delete(storageKey);
|
|
1021
|
+
}
|
|
1022
|
+
/** Clear all idempotency keys managed by this instance */
|
|
1023
|
+
clearAll() {
|
|
1024
|
+
for (const k of this._keys) this._storage.removeItem(k);
|
|
1025
|
+
this._keys.clear();
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Wrap a fetch-based function with automatic idempotency key injection.
|
|
1029
|
+
* Clears the key on success; keeps it on failure so retries reuse the same key.
|
|
1030
|
+
*/
|
|
1031
|
+
async wrap(operationKey, fn) {
|
|
1032
|
+
const key = this.generate(operationKey);
|
|
1033
|
+
try {
|
|
1034
|
+
const result = await fn(key);
|
|
1035
|
+
this.clear(operationKey);
|
|
1036
|
+
return result;
|
|
1037
|
+
} catch (e) {
|
|
1038
|
+
throw e;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
/** Remove all expired entries */
|
|
1042
|
+
gc() {
|
|
1043
|
+
const now = Date.now();
|
|
1044
|
+
for (const k of [...this._keys]) {
|
|
1045
|
+
const raw = this._storage.getItem(k);
|
|
1046
|
+
if (!raw) {
|
|
1047
|
+
this._keys.delete(k);
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
const e = JSON.parse(raw);
|
|
1052
|
+
if (now > e.expiresAt) {
|
|
1053
|
+
this._storage.removeItem(k);
|
|
1054
|
+
this._keys.delete(k);
|
|
1055
|
+
}
|
|
1056
|
+
} catch {
|
|
1057
|
+
this._storage.removeItem(k);
|
|
1058
|
+
this._keys.delete(k);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
function _generateKey() {
|
|
1064
|
+
const buf = new Uint8Array(16);
|
|
1065
|
+
crypto.getRandomValues(buf);
|
|
1066
|
+
return Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/api/http/http-client-extended.ts
|
|
1070
|
+
var _inFlight = /* @__PURE__ */ new Map();
|
|
1071
|
+
var JoopHttpClientExtended = class _JoopHttpClientExtended extends JoopHttpClient {
|
|
1072
|
+
// ── Parallel requests ───────────────────────────────────────────────────────
|
|
1073
|
+
/** Fire multiple GETs at once, collect all results (never throws). */
|
|
1074
|
+
async getAll(urls, options) {
|
|
1075
|
+
return Promise.all(
|
|
1076
|
+
urls.map(async (url, index) => {
|
|
1077
|
+
const t0 = Date.now();
|
|
1078
|
+
try {
|
|
1079
|
+
const data = await this.get(url, options);
|
|
1080
|
+
return { index, url, data, ok: true, ms: Date.now() - t0 };
|
|
1081
|
+
} catch (e) {
|
|
1082
|
+
return { index, url, error: e, ok: false, ms: Date.now() - t0 };
|
|
1083
|
+
}
|
|
1084
|
+
})
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
/** Post to multiple URLs in parallel (fire-and-collect). */
|
|
1088
|
+
async postAll(requests) {
|
|
1089
|
+
return Promise.all(
|
|
1090
|
+
requests.map(async (req, index) => {
|
|
1091
|
+
const t0 = Date.now();
|
|
1092
|
+
try {
|
|
1093
|
+
const data = await this.post(req.url, req.body, req.options);
|
|
1094
|
+
return { index, url: req.url, data, ok: true, ms: Date.now() - t0 };
|
|
1095
|
+
} catch (e) {
|
|
1096
|
+
return { index, url: req.url, error: e, ok: false, ms: Date.now() - t0 };
|
|
1097
|
+
}
|
|
1098
|
+
})
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
/** Race multiple GET requests — resolves with the first successful response. */
|
|
1102
|
+
async race(urls, options) {
|
|
1103
|
+
return new Promise((resolve, reject) => {
|
|
1104
|
+
let settled = false;
|
|
1105
|
+
const errors = [];
|
|
1106
|
+
urls.forEach((url) => {
|
|
1107
|
+
const t0 = Date.now();
|
|
1108
|
+
this.get(url, options).then((data) => {
|
|
1109
|
+
if (!settled) {
|
|
1110
|
+
settled = true;
|
|
1111
|
+
resolve({ data, url, ms: Date.now() - t0 });
|
|
1112
|
+
}
|
|
1113
|
+
}).catch((err) => {
|
|
1114
|
+
errors.push(err);
|
|
1115
|
+
if (errors.length === urls.length) reject(errors[0]);
|
|
1116
|
+
});
|
|
1117
|
+
});
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
/** Sequential requests — each result fed to a mapper for the next URL. */
|
|
1121
|
+
async chain(steps, options) {
|
|
1122
|
+
const results = [];
|
|
1123
|
+
let prev;
|
|
1124
|
+
for (const step of steps) {
|
|
1125
|
+
const { url, body, method = "GET" } = step(prev);
|
|
1126
|
+
prev = method === "POST" ? await this.post(url, body, options) : await this.get(url, options);
|
|
1127
|
+
results.push(prev);
|
|
1128
|
+
}
|
|
1129
|
+
return results;
|
|
1130
|
+
}
|
|
1131
|
+
// ── Deduplication ─────────────────────────────────────────────────────────
|
|
1132
|
+
/** GET with request deduplication — concurrent identical URLs share one network call. */
|
|
1133
|
+
getDedup(url, options) {
|
|
1134
|
+
const key = url + JSON.stringify(options?.headers ?? {});
|
|
1135
|
+
if (_inFlight.has(key)) return _inFlight.get(key);
|
|
1136
|
+
const promise = this.get(url, options).finally(() => _inFlight.delete(key));
|
|
1137
|
+
_inFlight.set(key, promise);
|
|
1138
|
+
return promise;
|
|
1139
|
+
}
|
|
1140
|
+
// ── Retry wrapper ─────────────────────────────────────────────────────────
|
|
1141
|
+
/** GET with automatic retry on failure. */
|
|
1142
|
+
getWithRetry(url, retry, options) {
|
|
1143
|
+
return withRetry(() => this.get(url, options), retry);
|
|
1144
|
+
}
|
|
1145
|
+
postWithRetry(url, body, retry, options) {
|
|
1146
|
+
return withRetry(() => this.post(url, body, options), retry);
|
|
1147
|
+
}
|
|
1148
|
+
// ── Request queue ──────────────────────────────────────────────────────────
|
|
1149
|
+
createQueue(config = { concurrency: 4 }) {
|
|
1150
|
+
return new JoopRequestQueue(config);
|
|
1151
|
+
}
|
|
1152
|
+
// ── Typed response helpers ────────────────────────────────────────────────
|
|
1153
|
+
/** GET that throws if the response does not pass a validator. */
|
|
1154
|
+
async getValidated(url, validate, options) {
|
|
1155
|
+
const data = await this.get(url, options);
|
|
1156
|
+
if (!validate(data)) throw new TypeError(`JoopHttp: response from ${url} failed type validation`);
|
|
1157
|
+
return data;
|
|
1158
|
+
}
|
|
1159
|
+
/** GET with fallback value on error (never throws). */
|
|
1160
|
+
async getSafe(url, fallback, options) {
|
|
1161
|
+
try {
|
|
1162
|
+
return await this.get(url, options);
|
|
1163
|
+
} catch {
|
|
1164
|
+
return fallback;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
/** POST with fallback on error. */
|
|
1168
|
+
async postSafe(url, body, fallback, options) {
|
|
1169
|
+
try {
|
|
1170
|
+
return await this.post(url, body, options);
|
|
1171
|
+
} catch {
|
|
1172
|
+
return fallback;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
// ── Timeout helper ────────────────────────────────────────────────────────
|
|
1176
|
+
/** Set a default timeout on all subsequent requests. */
|
|
1177
|
+
withTimeout(ms) {
|
|
1178
|
+
const child = new _JoopHttpClientExtended();
|
|
1179
|
+
const orig = child.request.bind(child);
|
|
1180
|
+
child.request = (method, url, opts = {}) => orig(method, url, { timeout: ms, ...opts });
|
|
1181
|
+
return child;
|
|
1182
|
+
}
|
|
1183
|
+
// ── Sync-style callback wrapper ───────────────────────────────────────────
|
|
1184
|
+
/** "Sync-style" GET — callback-based wrapper (JS is single-threaded; true sync fetch doesn't exist).
|
|
1185
|
+
* Use only in contexts where you cannot use async/await (e.g., legacy callbacks). */
|
|
1186
|
+
getCallback(url, onSuccess, onError, options) {
|
|
1187
|
+
this.get(url, options).then(onSuccess).catch(onError);
|
|
1188
|
+
}
|
|
1189
|
+
/** Batch multiple operations with concurrency limit. */
|
|
1190
|
+
async batch(items, fn, concurrency = 4) {
|
|
1191
|
+
const results = [];
|
|
1192
|
+
for (let i = 0; i < items.length; i += concurrency) {
|
|
1193
|
+
const slice = items.slice(i, i + concurrency);
|
|
1194
|
+
const batch = await Promise.all(
|
|
1195
|
+
slice.map(async (item, idx) => {
|
|
1196
|
+
try {
|
|
1197
|
+
const result = await fn(item, i + idx);
|
|
1198
|
+
return { item, result, ok: true };
|
|
1199
|
+
} catch (e) {
|
|
1200
|
+
return { item, error: e, ok: false };
|
|
1201
|
+
}
|
|
1202
|
+
})
|
|
1203
|
+
);
|
|
1204
|
+
results.push(...batch);
|
|
1205
|
+
}
|
|
1206
|
+
return results;
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
var JoopRequestQueue = class {
|
|
1210
|
+
_queue = [];
|
|
1211
|
+
_running = 0;
|
|
1212
|
+
_config;
|
|
1213
|
+
constructor(config) {
|
|
1214
|
+
this._config = { concurrency: config.concurrency ?? 4, onDrain: config.onDrain };
|
|
1215
|
+
}
|
|
1216
|
+
enqueue(fn) {
|
|
1217
|
+
return new Promise((resolve, reject) => {
|
|
1218
|
+
this._queue.push({ fn, resolve, reject });
|
|
1219
|
+
this._tick();
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
get size() {
|
|
1223
|
+
return this._queue.length;
|
|
1224
|
+
}
|
|
1225
|
+
get inflight() {
|
|
1226
|
+
return this._running;
|
|
1227
|
+
}
|
|
1228
|
+
_tick() {
|
|
1229
|
+
while (this._running < this._config.concurrency && this._queue.length > 0) {
|
|
1230
|
+
const entry = this._queue.shift();
|
|
1231
|
+
this._running++;
|
|
1232
|
+
entry.fn().then(entry.resolve, entry.reject).finally(() => {
|
|
1233
|
+
this._running--;
|
|
1234
|
+
this._tick();
|
|
1235
|
+
if (this._running === 0 && this._queue.length === 0) this._config.onDrain?.();
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
// src/api/http/interceptors.ts
|
|
1242
|
+
function bearerAuthInterceptor(getToken) {
|
|
1243
|
+
return (req) => {
|
|
1244
|
+
const token = getToken();
|
|
1245
|
+
if (token) {
|
|
1246
|
+
return { ...req, headers: { ...req.headers, Authorization: `Bearer ${token}` } };
|
|
1247
|
+
}
|
|
1248
|
+
return req;
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
function loggingRequestInterceptor(opts = {}) {
|
|
1252
|
+
const log = opts.logger ?? console;
|
|
1253
|
+
return (req) => {
|
|
1254
|
+
if (opts.logRequest !== false) {
|
|
1255
|
+
log.log(`[JoopHTTP] \u279C ${req.method} ${req.url}`, req.body ? { body: req.body } : "");
|
|
1256
|
+
}
|
|
1257
|
+
return req;
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
function loggingResponseInterceptor(opts = {}) {
|
|
1261
|
+
const log = opts.logger ?? console;
|
|
1262
|
+
return (res) => {
|
|
1263
|
+
if (opts.logResponse !== false) {
|
|
1264
|
+
log.log(`[JoopHTTP] \u2713 ${res.status}`, res.data);
|
|
1265
|
+
}
|
|
1266
|
+
return res;
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
function loggingErrorInterceptor(opts = {}) {
|
|
1270
|
+
const log = opts.logger ?? console;
|
|
1271
|
+
return (err) => {
|
|
1272
|
+
if (opts.logErrors !== false) {
|
|
1273
|
+
const e = err;
|
|
1274
|
+
log.error(`[JoopHTTP] \u2717 ${e?.status ?? 0} ${e?.message}`, err);
|
|
1275
|
+
}
|
|
1276
|
+
return err;
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
function correlationInterceptor(getCorrelationId) {
|
|
1280
|
+
const getId = getCorrelationId ?? (() => crypto.randomUUID?.() ?? Math.random().toString(36).slice(2));
|
|
1281
|
+
return (req) => ({
|
|
1282
|
+
...req,
|
|
1283
|
+
headers: {
|
|
1284
|
+
...req.headers,
|
|
1285
|
+
"X-Correlation-ID": getId(),
|
|
1286
|
+
"X-Request-ID": getId()
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
function baseUrlInterceptor(baseUrl) {
|
|
1291
|
+
const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
1292
|
+
return (req) => ({
|
|
1293
|
+
...req,
|
|
1294
|
+
url: req.url.startsWith("http") ? req.url : `${base}${req.url.startsWith("/") ? "" : "/"}${req.url}`
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
function retryErrorInterceptor(opts = {}) {
|
|
1298
|
+
const maxRetries = opts.maxRetries ?? 2;
|
|
1299
|
+
const retryOn = opts.retryOn ?? [429, 502, 503, 504];
|
|
1300
|
+
return (err) => {
|
|
1301
|
+
const e = err;
|
|
1302
|
+
const shouldRetry = retryOn.includes(e?.status ?? 0);
|
|
1303
|
+
return Object.assign(e ?? {}, { _joopShouldRetry: shouldRetry, _joopMaxRetries: maxRetries });
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
var _cache = /* @__PURE__ */ new Map();
|
|
1307
|
+
function cacheResponseInterceptor(opts = {}) {
|
|
1308
|
+
const ttl = opts.ttlMs ?? 6e4;
|
|
1309
|
+
const methods = (opts.methods ?? ["GET"]).map((m) => m.toUpperCase());
|
|
1310
|
+
return (res) => {
|
|
1311
|
+
if (methods.includes(res._requestMethod?.toUpperCase() ?? "GET")) {
|
|
1312
|
+
_cache.set(res._requestUrl ?? "", { data: res.data, expiresAt: Date.now() + ttl });
|
|
1313
|
+
}
|
|
1314
|
+
return res;
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
function cacheRequestInterceptor() {
|
|
1318
|
+
return (req) => {
|
|
1319
|
+
const cached = _cache.get(req.url);
|
|
1320
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
1321
|
+
return Object.assign(req, { _cacheHit: true, _cachedData: cached.data });
|
|
1322
|
+
}
|
|
1323
|
+
return req;
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
function clearCache(url) {
|
|
1327
|
+
url ? _cache.delete(url) : _cache.clear();
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
export { JoopCircuitBreaker, JoopExportService, JoopFileSaverService, JoopHttpClient, JoopHttpClientExtended, JoopIdempotencyService, JoopInterceptorPipeline, JoopOfflineQueue, JoopPollingService, JoopRequestQueue, JoopRequestService, JoopResponseMapper, JoopWebhookVerifier, baseUrlInterceptor, bearerAuthInterceptor, cacheRequestInterceptor, cacheResponseInterceptor, clearCache, correlationInterceptor, isHttpError, loggingErrorInterceptor, loggingRequestInterceptor, loggingResponseInterceptor, retryErrorInterceptor, withRetry };
|
|
1331
|
+
//# sourceMappingURL=index.mjs.map
|
|
1332
|
+
//# sourceMappingURL=index.mjs.map
|