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.
Files changed (259) hide show
  1. package/CHANGELOG.md +678 -0
  2. package/README.md +583 -0
  3. package/dist/a11y.service-C-DQQfgO.d.mts +143 -0
  4. package/dist/a11y.service-CauEJrJe.d.ts +143 -0
  5. package/dist/adapters-B6slG6hQ.d.mts +84 -0
  6. package/dist/adapters-B6slG6hQ.d.ts +84 -0
  7. package/dist/aes.service-CkoupAww.d.mts +95 -0
  8. package/dist/aes.service-CkoupAww.d.ts +95 -0
  9. package/dist/ai/index.d.mts +99 -0
  10. package/dist/ai/index.d.ts +99 -0
  11. package/dist/ai/index.js +307 -0
  12. package/dist/ai/index.js.map +1 -0
  13. package/dist/ai/index.mjs +304 -0
  14. package/dist/ai/index.mjs.map +1 -0
  15. package/dist/analytics/index.d.mts +42 -0
  16. package/dist/analytics/index.d.ts +42 -0
  17. package/dist/analytics/index.js +139 -0
  18. package/dist/analytics/index.js.map +1 -0
  19. package/dist/analytics/index.mjs +136 -0
  20. package/dist/analytics/index.mjs.map +1 -0
  21. package/dist/angular/index.d.mts +148 -0
  22. package/dist/angular/index.d.ts +148 -0
  23. package/dist/angular/index.js +122 -0
  24. package/dist/angular/index.js.map +1 -0
  25. package/dist/angular/index.mjs +101 -0
  26. package/dist/angular/index.mjs.map +1 -0
  27. package/dist/api/index.d.mts +128 -0
  28. package/dist/api/index.d.ts +128 -0
  29. package/dist/api/index.js +1358 -0
  30. package/dist/api/index.js.map +1 -0
  31. package/dist/api/index.mjs +1332 -0
  32. package/dist/api/index.mjs.map +1 -0
  33. package/dist/auth/index.d.mts +105 -0
  34. package/dist/auth/index.d.ts +105 -0
  35. package/dist/auth/index.js +989 -0
  36. package/dist/auth/index.js.map +1 -0
  37. package/dist/auth/index.mjs +979 -0
  38. package/dist/auth/index.mjs.map +1 -0
  39. package/dist/auth.service-DNVB-L4U.d.mts +16 -0
  40. package/dist/auth.service-PjUUSUIt.d.ts +16 -0
  41. package/dist/banking/index.d.mts +1530 -0
  42. package/dist/banking/index.d.ts +1530 -0
  43. package/dist/banking/index.js +4739 -0
  44. package/dist/banking/index.js.map +1 -0
  45. package/dist/banking/index.mjs +4661 -0
  46. package/dist/banking/index.mjs.map +1 -0
  47. package/dist/cache/index.d.mts +40 -0
  48. package/dist/cache/index.d.ts +40 -0
  49. package/dist/cache/index.js +174 -0
  50. package/dist/cache/index.js.map +1 -0
  51. package/dist/cache/index.mjs +172 -0
  52. package/dist/cache/index.mjs.map +1 -0
  53. package/dist/client-profile.service-BuPeXVp5.d.mts +28 -0
  54. package/dist/client-profile.service-D5bRRYQp.d.ts +28 -0
  55. package/dist/config.models-Cqg04fAQ.d.mts +84 -0
  56. package/dist/config.models-Cqg04fAQ.d.ts +84 -0
  57. package/dist/config.service-CrCvI-JS.d.ts +31 -0
  58. package/dist/config.service-Cz4QQLlf.d.mts +31 -0
  59. package/dist/core/index.d.mts +4 -0
  60. package/dist/core/index.d.ts +4 -0
  61. package/dist/core/index.js +631 -0
  62. package/dist/core/index.js.map +1 -0
  63. package/dist/core/index.mjs +619 -0
  64. package/dist/core/index.mjs.map +1 -0
  65. package/dist/crypto-utils-DriNhLdx.d.mts +30 -0
  66. package/dist/crypto-utils-DriNhLdx.d.ts +30 -0
  67. package/dist/data-storage.service-DT6xaTxE.d.ts +51 -0
  68. package/dist/data-storage.service-LvhGRCmw.d.mts +51 -0
  69. package/dist/deeplink/index.d.mts +39 -0
  70. package/dist/deeplink/index.d.ts +39 -0
  71. package/dist/deeplink/index.js +268 -0
  72. package/dist/deeplink/index.js.map +1 -0
  73. package/dist/deeplink/index.mjs +265 -0
  74. package/dist/deeplink/index.mjs.map +1 -0
  75. package/dist/deeplink.service-Ctd5u243.d.mts +35 -0
  76. package/dist/deeplink.service-uUuTnY9_.d.ts +35 -0
  77. package/dist/dev/index.d.mts +20 -0
  78. package/dist/dev/index.d.ts +20 -0
  79. package/dist/dev/index.js +51 -0
  80. package/dist/dev/index.js.map +1 -0
  81. package/dist/dev/index.mjs +49 -0
  82. package/dist/dev/index.mjs.map +1 -0
  83. package/dist/device/index.d.mts +108 -0
  84. package/dist/device/index.d.ts +108 -0
  85. package/dist/device/index.js +960 -0
  86. package/dist/device/index.js.map +1 -0
  87. package/dist/device/index.mjs +951 -0
  88. package/dist/device/index.mjs.map +1 -0
  89. package/dist/differential-privacy-BcAv1G80.d.mts +210 -0
  90. package/dist/differential-privacy-C8mAUjZr.d.ts +210 -0
  91. package/dist/encryption/index.d.mts +75 -0
  92. package/dist/encryption/index.d.ts +75 -0
  93. package/dist/encryption/index.js +605 -0
  94. package/dist/encryption/index.js.map +1 -0
  95. package/dist/encryption/index.mjs +598 -0
  96. package/dist/encryption/index.mjs.map +1 -0
  97. package/dist/form-validator-3tkmzr_o.d.mts +72 -0
  98. package/dist/form-validator-3tkmzr_o.d.ts +72 -0
  99. package/dist/forms/index.d.mts +59 -0
  100. package/dist/forms/index.d.ts +59 -0
  101. package/dist/forms/index.js +446 -0
  102. package/dist/forms/index.js.map +1 -0
  103. package/dist/forms/index.mjs +442 -0
  104. package/dist/forms/index.mjs.map +1 -0
  105. package/dist/i18n/index.d.mts +37 -0
  106. package/dist/i18n/index.d.ts +37 -0
  107. package/dist/i18n/index.js +147 -0
  108. package/dist/i18n/index.js.map +1 -0
  109. package/dist/i18n/index.mjs +145 -0
  110. package/dist/i18n/index.mjs.map +1 -0
  111. package/dist/idempotency.service-_6LqhivP.d.mts +372 -0
  112. package/dist/idempotency.service-eOKoISRD.d.ts +372 -0
  113. package/dist/index-B_ksKpS1.d.mts +202 -0
  114. package/dist/index-CqDKWTUP.d.mts +28 -0
  115. package/dist/index-CqDKWTUP.d.ts +28 -0
  116. package/dist/index-DFqEoX_l.d.ts +202 -0
  117. package/dist/index-Dz0gOur2.d.mts +36 -0
  118. package/dist/index-Dz0gOur2.d.ts +36 -0
  119. package/dist/index.d.mts +1336 -0
  120. package/dist/index.d.ts +1336 -0
  121. package/dist/index.js +19464 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/index.mjs +19155 -0
  124. package/dist/index.mjs.map +1 -0
  125. package/dist/india/index.d.mts +75 -0
  126. package/dist/india/index.d.ts +75 -0
  127. package/dist/india/index.js +325 -0
  128. package/dist/india/index.js.map +1 -0
  129. package/dist/india/index.mjs +303 -0
  130. package/dist/india/index.mjs.map +1 -0
  131. package/dist/joop-Bx7Iwj5p.d.mts +155 -0
  132. package/dist/joop-CA3DMeOO.d.ts +155 -0
  133. package/dist/native-bridge/index.d.mts +27 -0
  134. package/dist/native-bridge/index.d.ts +27 -0
  135. package/dist/native-bridge/index.js +98 -0
  136. package/dist/native-bridge/index.js.map +1 -0
  137. package/dist/native-bridge/index.mjs +96 -0
  138. package/dist/native-bridge/index.mjs.map +1 -0
  139. package/dist/network/index.d.mts +85 -0
  140. package/dist/network/index.d.ts +85 -0
  141. package/dist/network/index.js +454 -0
  142. package/dist/network/index.js.map +1 -0
  143. package/dist/network/index.mjs +451 -0
  144. package/dist/network/index.mjs.map +1 -0
  145. package/dist/network-monitor-BIwPSXme.d.mts +179 -0
  146. package/dist/network-monitor-Bqp2hvZr.d.ts +179 -0
  147. package/dist/notification.service-Dm4fvfZf.d.mts +25 -0
  148. package/dist/notification.service-tEMKatWJ.d.ts +25 -0
  149. package/dist/observability/index.d.mts +179 -0
  150. package/dist/observability/index.d.ts +179 -0
  151. package/dist/observability/index.js +559 -0
  152. package/dist/observability/index.js.map +1 -0
  153. package/dist/observability/index.mjs +552 -0
  154. package/dist/observability/index.mjs.map +1 -0
  155. package/dist/oidc-client-DIJcClmB.d.mts +190 -0
  156. package/dist/oidc-client-DxhyE59t.d.ts +190 -0
  157. package/dist/platform/index.d.mts +73 -0
  158. package/dist/platform/index.d.ts +73 -0
  159. package/dist/platform/index.js +127 -0
  160. package/dist/platform/index.js.map +1 -0
  161. package/dist/platform/index.mjs +125 -0
  162. package/dist/platform/index.mjs.map +1 -0
  163. package/dist/pwa/index.d.mts +31 -0
  164. package/dist/pwa/index.d.ts +31 -0
  165. package/dist/pwa/index.js +247 -0
  166. package/dist/pwa/index.js.map +1 -0
  167. package/dist/pwa/index.mjs +244 -0
  168. package/dist/pwa/index.mjs.map +1 -0
  169. package/dist/react/index.d.mts +133 -0
  170. package/dist/react/index.d.ts +133 -0
  171. package/dist/react/index.js +632 -0
  172. package/dist/react/index.js.map +1 -0
  173. package/dist/react/index.mjs +630 -0
  174. package/dist/react/index.mjs.map +1 -0
  175. package/dist/router/index.d.mts +39 -0
  176. package/dist/router/index.d.ts +39 -0
  177. package/dist/router/index.js +168 -0
  178. package/dist/router/index.js.map +1 -0
  179. package/dist/router/index.mjs +166 -0
  180. package/dist/router/index.mjs.map +1 -0
  181. package/dist/security/index.d.mts +206 -0
  182. package/dist/security/index.d.ts +206 -0
  183. package/dist/security/index.js +1297 -0
  184. package/dist/security/index.js.map +1 -0
  185. package/dist/security/index.mjs +1285 -0
  186. package/dist/security/index.mjs.map +1 -0
  187. package/dist/session/index.d.mts +115 -0
  188. package/dist/session/index.d.ts +115 -0
  189. package/dist/session/index.js +297 -0
  190. package/dist/session/index.js.map +1 -0
  191. package/dist/session/index.mjs +292 -0
  192. package/dist/session/index.mjs.map +1 -0
  193. package/dist/state/index.d.mts +43 -0
  194. package/dist/state/index.d.ts +43 -0
  195. package/dist/state/index.js +156 -0
  196. package/dist/state/index.js.map +1 -0
  197. package/dist/state/index.mjs +152 -0
  198. package/dist/state/index.mjs.map +1 -0
  199. package/dist/statement-parser-BHQtXwCM.d.ts +260 -0
  200. package/dist/statement-parser-C2qNmb49.d.mts +260 -0
  201. package/dist/storage/index.d.mts +40 -0
  202. package/dist/storage/index.d.ts +40 -0
  203. package/dist/storage/index.js +256 -0
  204. package/dist/storage/index.js.map +1 -0
  205. package/dist/storage/index.mjs +252 -0
  206. package/dist/storage/index.mjs.map +1 -0
  207. package/dist/sync/index.d.mts +69 -0
  208. package/dist/sync/index.d.ts +69 -0
  209. package/dist/sync/index.js +330 -0
  210. package/dist/sync/index.js.map +1 -0
  211. package/dist/sync/index.mjs +323 -0
  212. package/dist/sync/index.mjs.map +1 -0
  213. package/dist/sync-engine-DCIMRG5s.d.ts +61 -0
  214. package/dist/sync-engine-DZqyKHkK.d.mts +61 -0
  215. package/dist/theme/index.d.mts +53 -0
  216. package/dist/theme/index.d.ts +53 -0
  217. package/dist/theme/index.js +169 -0
  218. package/dist/theme/index.js.map +1 -0
  219. package/dist/theme/index.mjs +167 -0
  220. package/dist/theme/index.mjs.map +1 -0
  221. package/dist/ui/index.d.mts +66 -0
  222. package/dist/ui/index.d.ts +66 -0
  223. package/dist/ui/index.js +811 -0
  224. package/dist/ui/index.js.map +1 -0
  225. package/dist/ui/index.mjs +803 -0
  226. package/dist/ui/index.mjs.map +1 -0
  227. package/dist/utilities/index.d.mts +199 -0
  228. package/dist/utilities/index.d.ts +199 -0
  229. package/dist/utilities/index.js +1991 -0
  230. package/dist/utilities/index.js.map +1 -0
  231. package/dist/utilities/index.mjs +1923 -0
  232. package/dist/utilities/index.mjs.map +1 -0
  233. package/dist/validation/index.d.mts +60 -0
  234. package/dist/validation/index.d.ts +60 -0
  235. package/dist/validation/index.js +460 -0
  236. package/dist/validation/index.js.map +1 -0
  237. package/dist/validation/index.mjs +455 -0
  238. package/dist/validation/index.mjs.map +1 -0
  239. package/dist/vue/index.d.mts +135 -0
  240. package/dist/vue/index.d.ts +135 -0
  241. package/dist/vue/index.js +621 -0
  242. package/dist/vue/index.js.map +1 -0
  243. package/dist/vue/index.mjs +619 -0
  244. package/dist/vue/index.mjs.map +1 -0
  245. package/dist/watermark.service-Detur5tq.d.ts +235 -0
  246. package/dist/watermark.service-QNegMeQZ.d.mts +235 -0
  247. package/dist/workers/index.d.mts +42 -0
  248. package/dist/workers/index.d.ts +42 -0
  249. package/dist/workers/index.js +359 -0
  250. package/dist/workers/index.js.map +1 -0
  251. package/dist/workers/index.mjs +356 -0
  252. package/dist/workers/index.mjs.map +1 -0
  253. package/dist/workflow/index.d.mts +99 -0
  254. package/dist/workflow/index.d.ts +99 -0
  255. package/dist/workflow/index.js +282 -0
  256. package/dist/workflow/index.js.map +1 -0
  257. package/dist/workflow/index.mjs +279 -0
  258. package/dist/workflow/index.mjs.map +1 -0
  259. package/package.json +226 -0
@@ -0,0 +1,979 @@
1
+ // src/events/index.ts
2
+ var JoopSubject = class {
3
+ _listeners = [];
4
+ subscribe(listener) {
5
+ this._listeners.push(listener);
6
+ return () => {
7
+ this._listeners = this._listeners.filter((l) => l !== listener);
8
+ };
9
+ }
10
+ next(value) {
11
+ for (const listener of this._listeners) {
12
+ listener(value);
13
+ }
14
+ }
15
+ asObservable() {
16
+ return new JoopObservable((listener) => this.subscribe(listener));
17
+ }
18
+ };
19
+ var JoopBehaviorSubject = class extends JoopSubject {
20
+ _value;
21
+ constructor(initialValue) {
22
+ super();
23
+ this._value = initialValue;
24
+ }
25
+ getValue() {
26
+ return this._value;
27
+ }
28
+ next(value) {
29
+ this._value = value;
30
+ super.next(value);
31
+ }
32
+ subscribe(listener) {
33
+ listener(this._value);
34
+ return super.subscribe(listener);
35
+ }
36
+ asObservable() {
37
+ return new JoopObservable((listener) => this.subscribe(listener));
38
+ }
39
+ };
40
+ var JoopObservable = class {
41
+ constructor(_subscribeFn) {
42
+ this._subscribeFn = _subscribeFn;
43
+ }
44
+ _subscribeFn;
45
+ subscribe(listener) {
46
+ return this._subscribeFn(listener);
47
+ }
48
+ /** Returns the current value without subscribing (only meaningful for BehaviorSubject-backed observables). */
49
+ getOnce() {
50
+ let result;
51
+ const unsub = this.subscribe((v) => {
52
+ result = v;
53
+ });
54
+ unsub();
55
+ return result;
56
+ }
57
+ };
58
+
59
+ // src/auth/auth.service.ts
60
+ var JoopAuthService = class {
61
+ constructor(_storage) {
62
+ this._storage = _storage;
63
+ }
64
+ _storage;
65
+ _loggedIn$ = new JoopBehaviorSubject(false);
66
+ setLoggedIn(isLoggedIn, menuData = { menuObj: [] }) {
67
+ this._loggedIn$.next(isLoggedIn);
68
+ if (isLoggedIn) {
69
+ this._storage.setMenuData(menuData);
70
+ } else {
71
+ this._storage.clearMenuData();
72
+ }
73
+ }
74
+ isLoggedIn() {
75
+ return this._loggedIn$.getValue();
76
+ }
77
+ isLoggedIn$() {
78
+ return this._loggedIn$.asObservable();
79
+ }
80
+ logout() {
81
+ this.setLoggedIn(false);
82
+ }
83
+ };
84
+
85
+ // src/auth/token.service.ts
86
+ var JoopTokenService = class {
87
+ _access = "";
88
+ _refresh = "";
89
+ _refreshCb = null;
90
+ _timer = null;
91
+ setTokens(accessToken, refreshToken) {
92
+ this._access = accessToken;
93
+ this._refresh = refreshToken;
94
+ this._scheduleRefresh();
95
+ }
96
+ getAccessToken() {
97
+ return this._access;
98
+ }
99
+ getRefreshToken() {
100
+ return this._refresh;
101
+ }
102
+ decode(token) {
103
+ try {
104
+ const parts = token.split(".");
105
+ if (parts.length !== 3) return null;
106
+ const padded = parts[1].replace(/-/g, "+").replace(/_/g, "/");
107
+ return JSON.parse(atob(padded));
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+ isExpired(token) {
113
+ const payload = this.decode(token ?? this._access);
114
+ if (!payload?.exp) return true;
115
+ return Date.now() >= payload.exp * 1e3;
116
+ }
117
+ expiresInMs(token) {
118
+ const payload = this.decode(token ?? this._access);
119
+ if (!payload?.exp) return 0;
120
+ return Math.max(0, payload.exp * 1e3 - Date.now());
121
+ }
122
+ onRefreshNeeded(callback) {
123
+ this._refreshCb = callback;
124
+ }
125
+ async refreshNow() {
126
+ if (!this._refreshCb) throw new Error("No refresh callback registered");
127
+ const newToken = await this._refreshCb(this._refresh);
128
+ this._access = newToken;
129
+ this._scheduleRefresh();
130
+ return newToken;
131
+ }
132
+ clear() {
133
+ this._access = "";
134
+ this._refresh = "";
135
+ if (this._timer) clearTimeout(this._timer);
136
+ this._timer = null;
137
+ }
138
+ _scheduleRefresh() {
139
+ if (this._timer) clearTimeout(this._timer);
140
+ const ms = this.expiresInMs();
141
+ const delay = ms > 3e4 ? ms - 3e4 : 0;
142
+ if (delay > 0 && this._refreshCb) {
143
+ this._timer = setTimeout(() => this.refreshNow().catch(() => {
144
+ }), delay);
145
+ }
146
+ }
147
+ };
148
+
149
+ // src/auth/pkce.service.ts
150
+ var JoopPKCEService = class {
151
+ _verifier = "";
152
+ async generate() {
153
+ const array = new Uint8Array(32);
154
+ crypto.getRandomValues(array);
155
+ const verifier = this._base64url(array);
156
+ this._verifier = verifier;
157
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
158
+ const challenge = this._base64url(new Uint8Array(digest));
159
+ return { codeVerifier: verifier, codeChallenge: challenge, codeChallengeMethod: "S256" };
160
+ }
161
+ getVerifier() {
162
+ return this._verifier;
163
+ }
164
+ generateState() {
165
+ const arr = new Uint8Array(16);
166
+ crypto.getRandomValues(arr);
167
+ return this._base64url(arr);
168
+ }
169
+ buildAuthUrl(baseUrl, params) {
170
+ const q = new URLSearchParams({
171
+ response_type: "code",
172
+ client_id: params.clientId,
173
+ redirect_uri: params.redirectUri,
174
+ scope: params.scope,
175
+ state: params.state,
176
+ code_challenge: params.codeChallenge,
177
+ code_challenge_method: "S256"
178
+ });
179
+ return `${baseUrl}?${q.toString()}`;
180
+ }
181
+ _base64url(buf) {
182
+ return btoa(String.fromCharCode(...buf)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
183
+ }
184
+ };
185
+
186
+ // src/auth/otp.service.ts
187
+ var JoopOtpService = class {
188
+ _period = 30;
189
+ _digits = 6;
190
+ async generateTOTP(secretBase32, time) {
191
+ const key = this._base32Decode(secretBase32);
192
+ const counter = Math.floor((time ?? Date.now()) / 1e3 / this._period);
193
+ return this._hotp(key, counter);
194
+ }
195
+ async verifyTOTP(token, secretBase32, window2 = 1) {
196
+ const now = Math.floor(Date.now() / 1e3 / this._period);
197
+ for (let i = -window2; i <= window2; i++) {
198
+ const expected = await this._hotp(this._base32Decode(secretBase32), now + i);
199
+ if (expected === token) return true;
200
+ }
201
+ return false;
202
+ }
203
+ generateNumericOTP(length = 6) {
204
+ const arr = new Uint8Array(length);
205
+ crypto.getRandomValues(arr);
206
+ return Array.from(arr).map((b) => b % 10).join("");
207
+ }
208
+ generateAlphanumericOTP(length = 8) {
209
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
210
+ const arr = new Uint8Array(length);
211
+ crypto.getRandomValues(arr);
212
+ return Array.from(arr).map((b) => chars[b % chars.length]).join("");
213
+ }
214
+ async _hotp(key, counter) {
215
+ const buf = new ArrayBuffer(8);
216
+ new DataView(buf).setUint32(4, counter, false);
217
+ const safeKey = new Uint8Array(new ArrayBuffer(key.length));
218
+ safeKey.set(key);
219
+ const cryptoKey = await crypto.subtle.importKey("raw", safeKey, { name: "HMAC", hash: "SHA-1" }, false, ["sign"]);
220
+ const sig = new Uint8Array(await crypto.subtle.sign("HMAC", cryptoKey, buf));
221
+ const offset = sig[19] & 15;
222
+ const code = (sig[offset] & 127) << 24 | sig[offset + 1] << 16 | sig[offset + 2] << 8 | sig[offset + 3];
223
+ return String(code % Math.pow(10, this._digits)).padStart(this._digits, "0");
224
+ }
225
+ _base32Decode(s) {
226
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
227
+ const clean = s.toUpperCase().replace(/=+$/, "");
228
+ const bits = clean.split("").map((c) => alphabet.indexOf(c).toString(2).padStart(5, "0")).join("");
229
+ return new Uint8Array(bits.match(/.{8}/g)?.map((b) => parseInt(b, 2)) ?? []);
230
+ }
231
+ };
232
+
233
+ // src/auth/otp-timer.service.ts
234
+ var JoopOtpTimer = class {
235
+ _duration;
236
+ _resendCooldown;
237
+ _remaining = 0;
238
+ _cooldownRemaining = 0;
239
+ _phase = "idle";
240
+ _ticker = null;
241
+ _cooldownTicker = null;
242
+ _state$;
243
+ _onExpire = null;
244
+ _onTick = null;
245
+ constructor(config = {}) {
246
+ this._duration = config.duration ?? 60;
247
+ this._resendCooldown = config.resendCooldown ?? 30;
248
+ this._state$ = new JoopBehaviorSubject(this._snapshot());
249
+ if (config.autoStart) this.start();
250
+ }
251
+ start() {
252
+ this._clearTickers();
253
+ this._remaining = this._duration;
254
+ this._phase = "counting";
255
+ this._emit();
256
+ this._ticker = setInterval(() => {
257
+ this._remaining--;
258
+ this._onTick?.(this._remaining);
259
+ if (this._remaining <= 0) {
260
+ this._clearTickers();
261
+ this._phase = "expired";
262
+ this._remaining = 0;
263
+ this._onExpire?.();
264
+ }
265
+ this._emit();
266
+ }, 1e3);
267
+ }
268
+ resend() {
269
+ if (!this.canResend) return false;
270
+ this._startCooldown();
271
+ this.start();
272
+ return true;
273
+ }
274
+ stop() {
275
+ this._clearTickers();
276
+ this._phase = "idle";
277
+ this._remaining = 0;
278
+ this._emit();
279
+ }
280
+ reset() {
281
+ this.stop();
282
+ }
283
+ onExpire(fn) {
284
+ this._onExpire = fn;
285
+ return this;
286
+ }
287
+ onTick(fn) {
288
+ this._onTick = fn;
289
+ return this;
290
+ }
291
+ get remaining() {
292
+ return this._remaining;
293
+ }
294
+ get phase() {
295
+ return this._phase;
296
+ }
297
+ get isExpired() {
298
+ return this._phase === "expired";
299
+ }
300
+ get isCounting() {
301
+ return this._phase === "counting";
302
+ }
303
+ get canResend() {
304
+ return this._phase !== "cooldown" && this._cooldownRemaining === 0;
305
+ }
306
+ remaining$() {
307
+ return this._state$;
308
+ }
309
+ progress() {
310
+ if (this._duration === 0) return 0;
311
+ return Math.max(0, Math.min(1, this._remaining / this._duration));
312
+ }
313
+ destroy() {
314
+ this._clearTickers();
315
+ }
316
+ _startCooldown() {
317
+ this._cooldownRemaining = this._resendCooldown;
318
+ this._cooldownTicker = setInterval(() => {
319
+ this._cooldownRemaining--;
320
+ if (this._cooldownRemaining <= 0) {
321
+ this._cooldownRemaining = 0;
322
+ clearInterval(this._cooldownTicker);
323
+ this._cooldownTicker = null;
324
+ }
325
+ this._emit();
326
+ }, 1e3);
327
+ }
328
+ _clearTickers() {
329
+ if (this._ticker) {
330
+ clearInterval(this._ticker);
331
+ this._ticker = null;
332
+ }
333
+ }
334
+ _snapshot() {
335
+ return {
336
+ remaining: this._remaining,
337
+ phase: this._phase,
338
+ canResend: this.canResend,
339
+ cooldownRemaining: this._cooldownRemaining
340
+ };
341
+ }
342
+ _emit() {
343
+ this._state$.next(this._snapshot());
344
+ }
345
+ };
346
+
347
+ // src/auth/biometric.service.ts
348
+ var JoopBiometricService = class {
349
+ isSupported() {
350
+ return typeof window !== "undefined" && "credentials" in navigator && "PublicKeyCredential" in window;
351
+ }
352
+ async isAvailable() {
353
+ if (!this.isSupported()) return false;
354
+ return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
355
+ }
356
+ async register(options) {
357
+ const createOptions = {
358
+ challenge: options.challenge,
359
+ rp: { id: options.rpId, name: options.rpName },
360
+ user: {
361
+ id: new TextEncoder().encode(options.userId),
362
+ name: options.userName,
363
+ displayName: options.userDisplayName
364
+ },
365
+ pubKeyCredParams: [
366
+ { alg: -7, type: "public-key" },
367
+ // ES256
368
+ { alg: -257, type: "public-key" }
369
+ // RS256
370
+ ],
371
+ authenticatorSelection: {
372
+ authenticatorAttachment: "platform",
373
+ userVerification: "required"
374
+ },
375
+ timeout: 6e4
376
+ };
377
+ return navigator.credentials.create({ publicKey: createOptions });
378
+ }
379
+ async authenticate(options) {
380
+ const getOptions = {
381
+ challenge: options.challenge,
382
+ rpId: options.rpId,
383
+ allowCredentials: options.allowCredentials ?? [],
384
+ userVerification: "required",
385
+ timeout: 6e4
386
+ };
387
+ return navigator.credentials.get({ publicKey: getOptions });
388
+ }
389
+ generateChallenge() {
390
+ const buf = new Uint8Array(32);
391
+ crypto.getRandomValues(buf);
392
+ return buf.buffer;
393
+ }
394
+ credentialIdToBase64(id) {
395
+ return btoa(String.fromCharCode(...new Uint8Array(id)));
396
+ }
397
+ };
398
+
399
+ // src/auth/mfa.service.ts
400
+ var BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
401
+ var JoopMfaService = class {
402
+ _otp = new JoopOtpService();
403
+ _bio = new JoopBiometricService();
404
+ _challenges = /* @__PURE__ */ new Map();
405
+ _expired$ = new JoopSubject();
406
+ // ── TOTP ──────────────────────────────────────────────────────────────────
407
+ generateTotpSecret(length = 20) {
408
+ const bytes = crypto.getRandomValues(new Uint8Array(length));
409
+ let result = "";
410
+ for (const byte of bytes) result += BASE32_CHARS[byte % 32];
411
+ return result;
412
+ }
413
+ getTotpUri(secret, account, issuer = "JoopApp") {
414
+ const label = encodeURIComponent(`${issuer}:${account}`);
415
+ return `otpauth://totp/${label}?secret=${secret}&issuer=${encodeURIComponent(issuer)}&algorithm=SHA1&digits=6&period=30`;
416
+ }
417
+ async setupTotp(account, issuer) {
418
+ const secret = this.generateTotpSecret();
419
+ const uri = this.getTotpUri(secret, account, issuer);
420
+ const backupCodes = this.generateBackupCodes();
421
+ return { secret, uri, backupCodes };
422
+ }
423
+ async verifyTotp(token, secret, window2 = 1) {
424
+ return this._otp.verifyTOTP(token, secret, window2);
425
+ }
426
+ // ── OTP Challenge (SMS/Email) ──────────────────────────────────────────────
427
+ createChallenge(destination, channel, ttlMs = 5 * 60 * 1e3, maxAttempts = 3) {
428
+ const code = this._otp.generateNumericOTP(6);
429
+ const challenge = {
430
+ id: crypto.randomUUID(),
431
+ channel,
432
+ destination,
433
+ expiresAt: Date.now() + ttlMs,
434
+ attempts: 0,
435
+ maxAttempts,
436
+ verified: false,
437
+ _code: code
438
+ };
439
+ this._challenges.set(challenge.id, challenge);
440
+ setTimeout(() => {
441
+ if (!this._challenges.get(challenge.id)?.verified) {
442
+ this._challenges.delete(challenge.id);
443
+ this._expired$.next(challenge.id);
444
+ }
445
+ }, ttlMs);
446
+ return challenge;
447
+ }
448
+ /** Get the OTP code for a challenge (pass to your SMS/email sendFn) */
449
+ getChallengeOtp(challengeId) {
450
+ return this._challenges.get(challengeId)?._code ?? null;
451
+ }
452
+ verifyChallenge(challengeId, token) {
453
+ const ch = this._challenges.get(challengeId);
454
+ if (!ch) return { success: false, reason: "challenge_not_found" };
455
+ if (Date.now() > ch.expiresAt) {
456
+ this._challenges.delete(challengeId);
457
+ return { success: false, reason: "expired" };
458
+ }
459
+ if (ch.verified) return { success: false, reason: "already_verified" };
460
+ ch.attempts++;
461
+ if (ch._code !== token) {
462
+ const attemptsLeft = ch.maxAttempts - ch.attempts;
463
+ if (attemptsLeft <= 0) this._challenges.delete(challengeId);
464
+ return { success: false, reason: "invalid_code", attemptsLeft };
465
+ }
466
+ ch.verified = true;
467
+ return { success: true };
468
+ }
469
+ // ── Backup Codes ──────────────────────────────────────────────────────────
470
+ generateBackupCodes(count = 10, length = 8) {
471
+ return Array.from({ length: count }, () => this._otp.generateAlphanumericOTP(length));
472
+ }
473
+ async hashBackupCodes(codes) {
474
+ const hashed = [];
475
+ for (const code of codes) {
476
+ const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(code));
477
+ hashed.push(Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join(""));
478
+ }
479
+ return hashed;
480
+ }
481
+ async verifyBackupCode(code, hashedCodes) {
482
+ const hash = Array.from(
483
+ new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(code)))
484
+ ).map((b) => b.toString(16).padStart(2, "0")).join("");
485
+ const idx = hashedCodes.indexOf(hash);
486
+ if (idx === -1) return { valid: false, remaining: hashedCodes };
487
+ return { valid: true, remaining: hashedCodes.filter((_, i) => i !== idx) };
488
+ }
489
+ // ── Biometric ─────────────────────────────────────────────────────────────
490
+ async isBiometricAvailable() {
491
+ return this._bio.isAvailable();
492
+ }
493
+ async registerBiometric(userId, displayName) {
494
+ const challengeBytes = crypto.getRandomValues(new Uint8Array(32));
495
+ return this._bio.register({
496
+ challenge: challengeBytes.buffer,
497
+ rpId: window.location.hostname,
498
+ rpName: document.title || "JoopApp",
499
+ userId,
500
+ userName: userId,
501
+ userDisplayName: displayName ?? userId
502
+ });
503
+ }
504
+ async verifyBiometric(credentialId) {
505
+ try {
506
+ const challengeBytes = crypto.getRandomValues(new Uint8Array(32));
507
+ const result = await this._bio.authenticate({
508
+ challenge: challengeBytes.buffer,
509
+ rpId: window.location.hostname,
510
+ allowCredentials: [{ id: new TextEncoder().encode(credentialId), type: "public-key" }]
511
+ });
512
+ return !!result;
513
+ } catch {
514
+ return false;
515
+ }
516
+ }
517
+ // ── Challenge state ────────────────────────────────────────────────────────
518
+ onChallengeExpired(handler) {
519
+ return this._expired$.subscribe(handler);
520
+ }
521
+ clearExpiredChallenges() {
522
+ const now = Date.now();
523
+ let removed = 0;
524
+ for (const [id, ch] of this._challenges) {
525
+ if (now > ch.expiresAt) {
526
+ this._challenges.delete(id);
527
+ removed++;
528
+ }
529
+ }
530
+ return removed;
531
+ }
532
+ getChallenge(id) {
533
+ const ch = this._challenges.get(id);
534
+ if (!ch) return null;
535
+ const { _code: _, ...safe } = ch;
536
+ return safe;
537
+ }
538
+ };
539
+
540
+ // src/platform/platform.ts
541
+ var _webStorage = (store) => ({
542
+ getItem: (k) => store.getItem(k),
543
+ setItem: (k, v) => store.setItem(k, v),
544
+ removeItem: (k) => store.removeItem(k),
545
+ clear: () => store.clear()
546
+ });
547
+ var _MemStorageImpl = class {
548
+ _m = /* @__PURE__ */ new Map();
549
+ getItem(k) {
550
+ return this._m.get(k) ?? null;
551
+ }
552
+ setItem(k, v) {
553
+ this._m.set(k, v);
554
+ }
555
+ removeItem(k) {
556
+ this._m.delete(k);
557
+ }
558
+ clear() {
559
+ this._m.clear();
560
+ }
561
+ };
562
+ function _detect() {
563
+ if (typeof navigator !== "undefined" && navigator.product === "ReactNative") return "react-native";
564
+ const _proc = globalThis["process"];
565
+ if (typeof window === "undefined" && _proc?.versions?.node) return "node";
566
+ if (typeof window !== "undefined") return "web";
567
+ return "unknown";
568
+ }
569
+ var JoopPlatform = class {
570
+ static _type = _detect();
571
+ static _adapter = {};
572
+ static _memStorage = null;
573
+ /**
574
+ * Call once at app startup to configure adapters for your platform.
575
+ *
576
+ * Web (default — no call needed):
577
+ * JoopPlatform.init();
578
+ *
579
+ * React Native with react-native-mmkv:
580
+ * import { MMKV } from 'react-native-mmkv';
581
+ * const mmkv = new MMKV();
582
+ * JoopPlatform.init({ adapter: { storage: mmkv } });
583
+ *
584
+ * React Native with custom compression (fflate):
585
+ * import { gzip, ungzip } from 'fflate';
586
+ * JoopPlatform.init({
587
+ * adapter: {
588
+ * compression: {
589
+ * compress: (d, fmt) => new Promise((res, rej) => gzip(d, (e, r) => e ? rej(e) : res(r))),
590
+ * decompress: (d, fmt) => new Promise((res, rej) => ungzip(d, (e, r) => e ? rej(e) : res(r))),
591
+ * },
592
+ * },
593
+ * });
594
+ */
595
+ static init(config = {}) {
596
+ if (config.platform) this._type = config.platform;
597
+ this._adapter = { ...this._adapter, ...config.adapter ?? {} };
598
+ }
599
+ /** Current detected or overridden platform type */
600
+ static get type() {
601
+ return this._type;
602
+ }
603
+ static is(type) {
604
+ return this._type === type;
605
+ }
606
+ static isMobile() {
607
+ return this._type === "react-native";
608
+ }
609
+ static isWeb() {
610
+ return this._type === "web";
611
+ }
612
+ static isNode() {
613
+ return this._type === "node";
614
+ }
615
+ /** Raw adapter config registered via init() */
616
+ static getAdapter() {
617
+ return this._adapter;
618
+ }
619
+ /**
620
+ * Returns the best available storage adapter:
621
+ * 1. Custom adapter registered via init()
622
+ * 2. localStorage (web)
623
+ * 3. In-memory fallback (Node / RN without adapter)
624
+ */
625
+ static getStorage() {
626
+ if (this._adapter.storage) return this._adapter.storage;
627
+ if (typeof localStorage !== "undefined") return _webStorage(localStorage);
628
+ if (!this._memStorage) this._memStorage = new _MemStorageImpl();
629
+ return this._memStorage;
630
+ }
631
+ /**
632
+ * Returns the best available session-scoped storage:
633
+ * 1. Custom adapter registered via init()
634
+ * 2. sessionStorage (web)
635
+ * 3. Shared in-memory fallback
636
+ */
637
+ static getSessionStorage() {
638
+ if (this._adapter.storage) return this._adapter.storage;
639
+ if (typeof sessionStorage !== "undefined") return _webStorage(sessionStorage);
640
+ if (!this._memStorage) this._memStorage = new _MemStorageImpl();
641
+ return this._memStorage;
642
+ }
643
+ /** What APIs are available in the current environment */
644
+ static capabilities() {
645
+ return {
646
+ compression: typeof CompressionStream !== "undefined" || !!this._adapter.compression,
647
+ workers: typeof Worker !== "undefined" || !!this._adapter.worker,
648
+ cryptoSubtle: !!(typeof globalThis !== "undefined" && globalThis.crypto?.subtle),
649
+ persistentStorage: typeof localStorage !== "undefined" || !!this._adapter.storage,
650
+ dom: typeof document !== "undefined",
651
+ serviceWorker: typeof navigator !== "undefined" && "serviceWorker" in navigator
652
+ };
653
+ }
654
+ /** Reset to auto-detected defaults — useful between tests */
655
+ static _reset() {
656
+ this._type = _detect();
657
+ this._adapter = {};
658
+ this._memStorage = null;
659
+ }
660
+ };
661
+
662
+ // src/auth/oidc-client.ts
663
+ var JoopOIDCClient = class {
664
+ _cfg;
665
+ _metadata = null;
666
+ _tokens$ = new JoopBehaviorSubject(null);
667
+ _storage;
668
+ _renewTimer = null;
669
+ _KEY = "joop_oidc_tokens";
670
+ _VER_KEY = "joop_oidc_verifier";
671
+ _STATE_KEY = "joop_oidc_state";
672
+ constructor(config) {
673
+ this._cfg = { scope: "openid profile email", responseType: "code", ...config };
674
+ this._storage = config.storage ?? JoopPlatform.getSessionStorage();
675
+ const stored = this._storage.getItem(this._KEY);
676
+ if (stored) {
677
+ try {
678
+ this._tokens$.next(JSON.parse(stored));
679
+ } catch {
680
+ }
681
+ }
682
+ }
683
+ async discover() {
684
+ if (this._metadata) return this._metadata;
685
+ const url = `${this._cfg.issuer}/.well-known/openid-configuration`;
686
+ const res = await fetch(url);
687
+ if (!res.ok) throw new Error(`OIDC discovery failed: ${res.status}`);
688
+ const json = await res.json();
689
+ this._metadata = {
690
+ authorizationEndpoint: json.authorization_endpoint,
691
+ tokenEndpoint: json.token_endpoint,
692
+ jwksUri: json.jwks_uri,
693
+ userInfoEndpoint: json.userinfo_endpoint,
694
+ endSessionEndpoint: json.end_session_endpoint
695
+ };
696
+ return this._metadata;
697
+ }
698
+ async login(options = {}) {
699
+ const meta = await this.discover();
700
+ const { verifier, challenge } = await _generatePKCE();
701
+ const state = options.state ?? _randomBase64(16);
702
+ this._storage.setItem(this._VER_KEY, verifier);
703
+ this._storage.setItem(this._STATE_KEY, state);
704
+ const params = new URLSearchParams({
705
+ response_type: this._cfg.responseType,
706
+ client_id: this._cfg.clientId,
707
+ redirect_uri: this._cfg.redirectUri,
708
+ scope: this._cfg.scope,
709
+ state,
710
+ code_challenge: challenge,
711
+ code_challenge_method: "S256",
712
+ nonce: options.nonce ?? _randomBase64(16),
713
+ ...options.prompt ? { prompt: options.prompt } : {},
714
+ ...options.loginHint ? { login_hint: options.loginHint } : {},
715
+ ...options.extraParams
716
+ });
717
+ if (typeof window !== "undefined") {
718
+ window.location.href = `${meta.authorizationEndpoint}?${params}`;
719
+ } else {
720
+ return Promise.resolve();
721
+ }
722
+ }
723
+ /** Returns the authorization URL without redirecting (useful for React Native) */
724
+ async buildLoginUrl(options = {}) {
725
+ const meta = await this.discover();
726
+ const { verifier, challenge } = await _generatePKCE();
727
+ const state = options.state ?? _randomBase64(16);
728
+ this._storage.setItem(this._VER_KEY, verifier);
729
+ this._storage.setItem(this._STATE_KEY, state);
730
+ const params = new URLSearchParams({
731
+ response_type: this._cfg.responseType,
732
+ client_id: this._cfg.clientId,
733
+ redirect_uri: this._cfg.redirectUri,
734
+ scope: this._cfg.scope,
735
+ state,
736
+ code_challenge: challenge,
737
+ code_challenge_method: "S256",
738
+ nonce: options.nonce ?? _randomBase64(16),
739
+ ...options.prompt ? { prompt: options.prompt } : {},
740
+ ...options.loginHint ? { login_hint: options.loginHint } : {},
741
+ ...options.extraParams
742
+ });
743
+ return `${meta.authorizationEndpoint}?${params}`;
744
+ }
745
+ async handleCallback(url = typeof location !== "undefined" ? location.href : "") {
746
+ const params = new URLSearchParams(url.split("?")[1] ?? "");
747
+ const code = params.get("code");
748
+ const state = params.get("state");
749
+ const error = params.get("error");
750
+ if (error) throw new Error(`OIDC error: ${error} \u2014 ${params.get("error_description") ?? ""}`);
751
+ if (!code) throw new Error("OIDC callback: missing code");
752
+ const storedState = this._storage.getItem(this._STATE_KEY);
753
+ if (storedState && state !== storedState) throw new Error("OIDC callback: state mismatch (CSRF?)");
754
+ const verifier = this._storage.getItem(this._VER_KEY) ?? "";
755
+ this._storage.removeItem(this._VER_KEY);
756
+ this._storage.removeItem(this._STATE_KEY);
757
+ const meta = await this.discover();
758
+ const res = await fetch(meta.tokenEndpoint, {
759
+ method: "POST",
760
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
761
+ body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: this._cfg.redirectUri, client_id: this._cfg.clientId, code_verifier: verifier })
762
+ });
763
+ if (!res.ok) throw new Error(`OIDC token exchange failed: ${await res.text()}`);
764
+ return this._storeTokens(await res.json());
765
+ }
766
+ async refreshToken() {
767
+ const tokens = this._tokens$.getValue();
768
+ if (!tokens?.refreshToken) throw new Error("No refresh token available");
769
+ const meta = await this.discover();
770
+ const res = await fetch(meta.tokenEndpoint, {
771
+ method: "POST",
772
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
773
+ body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: tokens.refreshToken, client_id: this._cfg.clientId })
774
+ });
775
+ if (!res.ok) {
776
+ this.logout();
777
+ throw new Error(`Token refresh failed: ${res.status}`);
778
+ }
779
+ return this._storeTokens(await res.json());
780
+ }
781
+ async getUserInfo() {
782
+ const meta = await this.discover();
783
+ if (!meta.userInfoEndpoint) throw new Error("No userInfo endpoint in OIDC metadata");
784
+ const tokens = this._tokens$.getValue();
785
+ if (!tokens) throw new Error("Not authenticated");
786
+ const res = await fetch(meta.userInfoEndpoint, { headers: { Authorization: `Bearer ${tokens.accessToken}` } });
787
+ if (!res.ok) throw new Error(`UserInfo failed: ${res.status}`);
788
+ return res.json();
789
+ }
790
+ async logout(options = {}) {
791
+ this._tokens$.next(null);
792
+ this._storage.removeItem(this._KEY);
793
+ if (this._renewTimer) {
794
+ clearTimeout(this._renewTimer);
795
+ this._renewTimer = null;
796
+ }
797
+ if (this._metadata?.endSessionEndpoint && typeof window !== "undefined") {
798
+ const params = new URLSearchParams({ client_id: this._cfg.clientId, ...options.redirectUri ? { post_logout_redirect_uri: options.redirectUri } : {} });
799
+ window.location.href = `${this._metadata.endSessionEndpoint}?${params}`;
800
+ }
801
+ }
802
+ silentRenew(renewBeforeMs = 6e4) {
803
+ const tokens = this._tokens$.getValue();
804
+ if (!tokens?.refreshToken) return;
805
+ const delay = Math.max(0, tokens.expiresAt - Date.now() - renewBeforeMs);
806
+ if (this._renewTimer) clearTimeout(this._renewTimer);
807
+ this._renewTimer = setTimeout(async () => {
808
+ try {
809
+ await this.refreshToken();
810
+ this.silentRenew(renewBeforeMs);
811
+ } catch {
812
+ this._tokens$.next(null);
813
+ }
814
+ }, delay);
815
+ }
816
+ isAuthenticated() {
817
+ const t = this._tokens$.getValue();
818
+ return !!t && t.expiresAt > Date.now();
819
+ }
820
+ getTokens() {
821
+ return this._tokens$.getValue();
822
+ }
823
+ tokens$() {
824
+ return this._tokens$.asObservable();
825
+ }
826
+ _storeTokens(json) {
827
+ const tokens = {
828
+ accessToken: json.access_token,
829
+ idToken: json.id_token,
830
+ refreshToken: json.refresh_token,
831
+ expiresAt: Date.now() + (json.expires_in ?? 3600) * 1e3,
832
+ tokenType: json.token_type ?? "Bearer",
833
+ scope: json.scope
834
+ };
835
+ this._tokens$.next(tokens);
836
+ this._storage.setItem(this._KEY, JSON.stringify(tokens));
837
+ return tokens;
838
+ }
839
+ };
840
+ async function _generatePKCE() {
841
+ const verifier = _randomBase64(32);
842
+ const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
843
+ const challenge = btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
844
+ return { verifier, challenge };
845
+ }
846
+ function _randomBase64(bytes) {
847
+ const buf = new Uint8Array(bytes);
848
+ crypto.getRandomValues(buf);
849
+ return btoa(String.fromCharCode(...buf)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
850
+ }
851
+
852
+ // src/auth/sso.service.ts
853
+ var JoopSsoService = class {
854
+ _config;
855
+ _token = null;
856
+ _storageKey;
857
+ constructor(config) {
858
+ this._config = config;
859
+ this._storageKey = config.storageKey ?? "__joop_sso_token";
860
+ this._loadFromStorage();
861
+ }
862
+ // ── Token management ────────────────────────────────────────────────────────
863
+ setToken(token) {
864
+ this._token = token;
865
+ sessionStorage.setItem(this._storageKey, JSON.stringify(token));
866
+ }
867
+ getToken() {
868
+ return this._token;
869
+ }
870
+ isAuthenticated() {
871
+ return !!this._token && this._token.expiresAt > Date.now();
872
+ }
873
+ clearToken() {
874
+ this._token = null;
875
+ sessionStorage.removeItem(this._storageKey);
876
+ }
877
+ // ── Token exchange (Authorization Code → Access Token) ─────────────────────
878
+ async exchange(req) {
879
+ if (!this._config.tokenEndpoint) throw new Error("JoopSso: tokenEndpoint not configured");
880
+ const body = {
881
+ grant_type: "authorization_code",
882
+ code: req.code,
883
+ redirect_uri: req.redirectUri
884
+ };
885
+ if (req.codeVerifier) body["code_verifier"] = req.codeVerifier;
886
+ const res = await fetch(this._config.tokenEndpoint, {
887
+ method: "POST",
888
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
889
+ body: new URLSearchParams(body).toString()
890
+ });
891
+ if (!res.ok) throw new Error(`JoopSso: token exchange failed (${res.status})`);
892
+ const data = await res.json();
893
+ const token = {
894
+ accessToken: data.access_token,
895
+ refreshToken: data.refresh_token,
896
+ idToken: data.id_token,
897
+ expiresAt: Date.now() + (data.expires_in ?? 3600) * 1e3,
898
+ scopes: (data.scope ?? "").split(" ").filter(Boolean)
899
+ };
900
+ this.setToken(token);
901
+ return token;
902
+ }
903
+ // ── Whitelist-based external link opening ───────────────────────────────────
904
+ /**
905
+ * Open an external URL only if its origin is in allowedOrigins.
906
+ * Prevents open-redirect attacks.
907
+ */
908
+ openExternal(url, target = "blank") {
909
+ if (!this._isOriginAllowed(url)) {
910
+ console.warn(`JoopSso: blocked external link to ${url} \u2014 origin not in allowedOrigins`);
911
+ return false;
912
+ }
913
+ const anchor = document.createElement("a");
914
+ anchor.href = url;
915
+ anchor.target = target === "self" ? "_self" : target === "parent" ? "_parent" : "_blank";
916
+ anchor.rel = "noopener noreferrer";
917
+ document.body.appendChild(anchor);
918
+ anchor.click();
919
+ anchor.remove();
920
+ return true;
921
+ }
922
+ /** Check whether a URL's origin is whitelisted. */
923
+ isOriginAllowed(url) {
924
+ return this._isOriginAllowed(url);
925
+ }
926
+ // ── Internal API passthrough ────────────────────────────────────────────────
927
+ /**
928
+ * Make an authenticated internal API call.
929
+ * Automatically attaches the SSO bearer token.
930
+ * Rejects if the URL does not match internalBases.
931
+ */
932
+ async callInternal(url, init = {}) {
933
+ if (!this._isInternalUrl(url)) {
934
+ throw new Error(`JoopSso: URL "${url}" is not in internalBases`);
935
+ }
936
+ const token = this.getToken();
937
+ const headers = new Headers(init.headers);
938
+ if (token?.accessToken) headers.set("Authorization", `Bearer ${token.accessToken}`);
939
+ const res = await fetch(url, { ...init, headers });
940
+ if (!res.ok) throw new Error(`JoopSso: internal API error ${res.status}`);
941
+ const text = await res.text();
942
+ return text ? JSON.parse(text) : void 0;
943
+ }
944
+ // ── SSO Logout ──────────────────────────────────────────────────────────────
945
+ logout(redirectUri) {
946
+ this.clearToken();
947
+ if (this._config.logoutUrl) {
948
+ const url = redirectUri ? `${this._config.logoutUrl}?post_logout_redirect_uri=${encodeURIComponent(redirectUri)}` : this._config.logoutUrl;
949
+ window.location.href = url;
950
+ }
951
+ }
952
+ // ── Helpers ─────────────────────────────────────────────────────────────────
953
+ _isOriginAllowed(url) {
954
+ try {
955
+ const origin = new URL(url).origin;
956
+ return this._config.allowedOrigins.some((allowed) => {
957
+ const aOrigin = allowed.endsWith("/") ? allowed.slice(0, -1) : allowed;
958
+ return origin === aOrigin || origin.endsWith("." + aOrigin);
959
+ });
960
+ } catch {
961
+ return false;
962
+ }
963
+ }
964
+ _isInternalUrl(url) {
965
+ if (!this._config.internalBases?.length) return true;
966
+ return this._config.internalBases.some((base) => url.startsWith(base));
967
+ }
968
+ _loadFromStorage() {
969
+ try {
970
+ const raw = sessionStorage.getItem(this._storageKey);
971
+ if (raw) this._token = JSON.parse(raw);
972
+ } catch {
973
+ }
974
+ }
975
+ };
976
+
977
+ export { JoopAuthService, JoopBiometricService, JoopMfaService, JoopOIDCClient, JoopOtpService, JoopOtpTimer, JoopPKCEService, JoopSsoService, JoopTokenService };
978
+ //# sourceMappingURL=index.mjs.map
979
+ //# sourceMappingURL=index.mjs.map