@mintplayer/ng-spark 22.29.0 → 22.31.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 (103) hide show
  1. package/README.md +3 -3
  2. package/auth/README.md +216 -0
  3. package/fesm2022/mintplayer-ng-spark-auth-account.mjs +2023 -0
  4. package/fesm2022/mintplayer-ng-spark-auth-account.mjs.map +1 -0
  5. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +96 -0
  6. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -0
  7. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs +200 -0
  8. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs.map +1 -0
  9. package/fesm2022/mintplayer-ng-spark-auth-core.mjs +452 -0
  10. package/fesm2022/mintplayer-ng-spark-auth-core.mjs.map +1 -0
  11. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs +201 -0
  12. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs.map +1 -0
  13. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +196 -0
  14. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs.map +1 -0
  15. package/fesm2022/mintplayer-ng-spark-auth-guards.mjs +16 -0
  16. package/fesm2022/mintplayer-ng-spark-auth-guards.mjs.map +1 -0
  17. package/fesm2022/mintplayer-ng-spark-auth-interceptors.mjs +18 -0
  18. package/fesm2022/mintplayer-ng-spark-auth-interceptors.mjs.map +1 -0
  19. package/fesm2022/mintplayer-ng-spark-auth-login.mjs +282 -0
  20. package/fesm2022/mintplayer-ng-spark-auth-login.mjs.map +1 -0
  21. package/fesm2022/mintplayer-ng-spark-auth-models.mjs +114 -0
  22. package/fesm2022/mintplayer-ng-spark-auth-models.mjs.map +1 -0
  23. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +43 -0
  24. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs.map +1 -0
  25. package/fesm2022/mintplayer-ng-spark-auth-register.mjs +216 -0
  26. package/fesm2022/mintplayer-ng-spark-auth-register.mjs.map +1 -0
  27. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +223 -0
  28. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs.map +1 -0
  29. package/fesm2022/mintplayer-ng-spark-auth-routes.mjs +194 -0
  30. package/fesm2022/mintplayer-ng-spark-auth-routes.mjs.map +1 -0
  31. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +210 -0
  32. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs.map +1 -0
  33. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +218 -0
  34. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs.map +1 -0
  35. package/fesm2022/mintplayer-ng-spark-auth.mjs +48 -0
  36. package/fesm2022/mintplayer-ng-spark-auth.mjs.map +1 -0
  37. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +368 -2
  38. package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
  39. package/fesm2022/mintplayer-ng-spark-grid.mjs +22 -9
  40. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  41. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +389 -0
  42. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs.map +1 -0
  43. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +204 -0
  44. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs.map +1 -0
  45. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +486 -0
  46. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs.map +1 -0
  47. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +350 -0
  48. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs.map +1 -0
  49. package/fesm2022/mintplayer-ng-spark-identity-provider.mjs +66 -0
  50. package/fesm2022/mintplayer-ng-spark-identity-provider.mjs.map +1 -0
  51. package/fesm2022/mintplayer-ng-spark-models.mjs +2 -1
  52. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  53. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +13 -3
  54. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  55. package/fesm2022/mintplayer-ng-spark-po-form.mjs +6 -3
  56. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  57. package/fesm2022/mintplayer-ng-spark-pwa.mjs +162 -0
  58. package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -0
  59. package/fesm2022/mintplayer-ng-spark-query-list.mjs +5 -2
  60. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  61. package/fesm2022/mintplayer-ng-spark-renderers.mjs +109 -4
  62. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  63. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +36 -6
  64. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
  65. package/fesm2022/mintplayer-ng-spark-services.mjs +48 -6
  66. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  67. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  68. package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
  69. package/package.json +101 -3
  70. package/types/mintplayer-ng-spark-auth-account.d.ts +255 -0
  71. package/types/mintplayer-ng-spark-auth-auth-bar.d.ts +22 -0
  72. package/types/mintplayer-ng-spark-auth-confirm-email.d.ts +21 -0
  73. package/types/mintplayer-ng-spark-auth-core.d.ts +197 -0
  74. package/types/mintplayer-ng-spark-auth-external-login.d.ts +132 -0
  75. package/types/mintplayer-ng-spark-auth-forgot-password.d.ts +18 -0
  76. package/types/mintplayer-ng-spark-auth-guards.d.ts +15 -0
  77. package/types/mintplayer-ng-spark-auth-interceptors.d.ts +2 -0
  78. package/types/mintplayer-ng-spark-auth-login.d.ts +46 -0
  79. package/types/mintplayer-ng-spark-auth-models.d.ts +489 -0
  80. package/types/mintplayer-ng-spark-auth-pipes.d.ts +8 -0
  81. package/types/mintplayer-ng-spark-auth-register.d.ts +21 -0
  82. package/types/mintplayer-ng-spark-auth-reset-password.d.ts +25 -0
  83. package/types/mintplayer-ng-spark-auth-routes.d.ts +154 -0
  84. package/types/mintplayer-ng-spark-auth-sign-in.d.ts +84 -0
  85. package/types/mintplayer-ng-spark-auth-two-factor.d.ts +23 -0
  86. package/types/mintplayer-ng-spark-auth.d.ts +24 -0
  87. package/types/mintplayer-ng-spark-client-operations.d.ts +148 -3
  88. package/types/mintplayer-ng-spark-grid.d.ts +19 -1
  89. package/types/mintplayer-ng-spark-identity-provider-connected-applications.d.ts +35 -0
  90. package/types/mintplayer-ng-spark-identity-provider-core.d.ts +144 -0
  91. package/types/mintplayer-ng-spark-identity-provider-developers.d.ts +55 -0
  92. package/types/mintplayer-ng-spark-identity-provider-management.d.ts +33 -0
  93. package/types/mintplayer-ng-spark-identity-provider.d.ts +64 -0
  94. package/types/mintplayer-ng-spark-models.d.ts +28 -0
  95. package/types/mintplayer-ng-spark-po-detail.d.ts +8 -0
  96. package/types/mintplayer-ng-spark-po-form.d.ts +2 -0
  97. package/types/mintplayer-ng-spark-pwa.d.ts +89 -0
  98. package/types/mintplayer-ng-spark-query-list.d.ts +2 -0
  99. package/types/mintplayer-ng-spark-renderers.d.ts +47 -0
  100. package/types/mintplayer-ng-spark-retry-action-modal.d.ts +1 -0
  101. package/types/mintplayer-ng-spark-services.d.ts +37 -1
  102. package/types/mintplayer-ng-spark-shell.d.ts +2 -2
  103. package/types/mintplayer-ng-spark.d.ts +4 -4
@@ -0,0 +1,452 @@
1
+ import * as i0 from "@angular/core";
2
+ import { Injectable, Injector, NgZone, computed, inject, signal } from "@angular/core";
3
+ import { HttpClient } from "@angular/common/http";
4
+ import { Router } from "@angular/router";
5
+ import { firstValueFrom } from "rxjs";
6
+ import { SPARK_AUTH_CONFIG, SPARK_EXTERNAL_LOGIN_ACK_TYPE, SPARK_EXTERNAL_LOGIN_CHANNEL, SPARK_EXTERNAL_LOGIN_ERRORS, SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE, SPARK_EXTERNAL_LOGIN_QUERY_PARAM, SPARK_EXTERNAL_LOGIN_TIMEOUT_MS, passkeysSupported, resolveSparkExternalLoginMode, sparkExternalLoginDoneKey, sparkExternalLoginNonce, sparkExternalLoginStorageKey, sparkPasskeyError } from "@mintplayer/ng-spark/auth/models";
7
+ const POPUP_CLOSE_POLL_MS = 400;
8
+ var SparkAuthService = class SparkAuthService {
9
+ http = inject(HttpClient);
10
+ config = inject(SPARK_AUTH_CONFIG);
11
+ zone = inject(NgZone);
12
+ currentUser = signal(null, ...ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []);
13
+ user = this.currentUser.asReadonly();
14
+ isAuthenticated = computed(() => this.currentUser()?.isAuthenticated === true, ...ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []);
15
+ injector = inject(Injector);
16
+ pendingExternalLogin = signal(false, ...ngDevMode ? [{ debugName: "pendingExternalLogin" }] : /* istanbul ignore next */ []);
17
+ externalLoginPending = this.pendingExternalLogin.asReadonly();
18
+ activeExternalAttempt = null;
19
+ constructor() {
20
+ this.checkAuth();
21
+ }
22
+ pendingTwoFactorLogin = null;
23
+ pendingRememberMe = true;
24
+ loginUrl(rememberMe) {
25
+ return `${this.config.apiBasePath}/login?useCookies=true${rememberMe ? "" : "&useSessionCookies=true"}`;
26
+ }
27
+ async login(email, password, rememberMe = true) {
28
+ this.pendingTwoFactorLogin = null;
29
+ this.pendingRememberMe = rememberMe;
30
+ try {
31
+ await firstValueFrom(this.http.post(this.loginUrl(rememberMe), {
32
+ email,
33
+ password
34
+ }));
35
+ } catch (err) {
36
+ if (err?.status === 401 && err?.error?.detail === "RequiresTwoFactor") this.pendingTwoFactorLogin = {
37
+ email,
38
+ password
39
+ };
40
+ throw err;
41
+ }
42
+ await this.csrfRefresh();
43
+ await this.checkAuth();
44
+ }
45
+ async loginTwoFactor(twoFactorCode, twoFactorRecoveryCode) {
46
+ await firstValueFrom(this.http.post(this.loginUrl(this.pendingRememberMe), {
47
+ ...this.pendingTwoFactorLogin,
48
+ twoFactorCode,
49
+ twoFactorRecoveryCode
50
+ }));
51
+ this.pendingRememberMe = true;
52
+ this.pendingTwoFactorLogin = null;
53
+ await this.csrfRefresh();
54
+ await this.checkAuth();
55
+ }
56
+ async register(email, password, userName) {
57
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/register`, {
58
+ email,
59
+ password,
60
+ userName
61
+ }));
62
+ }
63
+ async logout() {
64
+ this.pendingTwoFactorLogin = null;
65
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/logout`, {}));
66
+ await this.csrfRefresh();
67
+ this.currentUser.set(null);
68
+ }
69
+ capabilities() {
70
+ return this.capabilitiesInFlight ??= firstValueFrom(this.http.get(`${this.config.apiBasePath}/capabilities`)).finally(() => this.capabilitiesInFlight = null);
71
+ }
72
+ capabilitiesInFlight = null;
73
+ async csrfRefresh() {
74
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/csrf-refresh`, {}));
75
+ }
76
+ async checkAuth() {
77
+ try {
78
+ const user = await firstValueFrom(this.http.get(`${this.config.apiBasePath}/me`));
79
+ this.currentUser.set(user);
80
+ return user;
81
+ } catch {
82
+ this.currentUser.set(null);
83
+ return null;
84
+ }
85
+ }
86
+ loginWithProvider(provider, options = {}) {
87
+ return this.externalFlow("/external-login", provider, options);
88
+ }
89
+ async externalLogins() {
90
+ return await firstValueFrom(this.http.get(`${this.config.apiBasePath}/external-logins`));
91
+ }
92
+ linkProvider(provider, options = {}) {
93
+ return this.externalFlow("/external-logins/link", provider, options);
94
+ }
95
+ async unlinkProvider(provider, providerKey) {
96
+ const url = `${this.config.apiBasePath}/external-logins/unlink?provider=${encodeURIComponent(provider)}&providerKey=${encodeURIComponent(providerKey)}`;
97
+ try {
98
+ await firstValueFrom(this.http.post(url, {}));
99
+ await this.checkAuth();
100
+ return { success: true };
101
+ } catch (response) {
102
+ return {
103
+ success: false,
104
+ error: response?.error?.error ?? "unlink_failed"
105
+ };
106
+ }
107
+ }
108
+ takeExternalLoginResult() {
109
+ const router = this.injector.get(Router, null);
110
+ if (!router) return null;
111
+ const tree = router.parseUrl(router.url);
112
+ const raw = tree.queryParams[SPARK_EXTERNAL_LOGIN_QUERY_PARAM];
113
+ if (raw === void 0) return null;
114
+ if (this.externalLoginResultTakenFrom === router.url) return null;
115
+ const takenFrom = router.url;
116
+ this.externalLoginResultTakenFrom = takenFrom;
117
+ const { [SPARK_EXTERNAL_LOGIN_QUERY_PARAM]: _, ...rest } = tree.queryParams;
118
+ tree.queryParams = rest;
119
+ queueMicrotask(() => void router.navigateByUrl(tree, { replaceUrl: true }).finally(() => {
120
+ if (this.externalLoginResultTakenFrom === takenFrom) this.externalLoginResultTakenFrom = null;
121
+ }));
122
+ const code = Array.isArray(raw) ? raw[0] : raw;
123
+ return SPARK_EXTERNAL_LOGIN_ERRORS.includes(code) ? code : "no_login_info";
124
+ }
125
+ externalLoginResultTakenFrom = null;
126
+ currentUrlForExternalLoginError() {
127
+ const router = this.injector.get(Router, null);
128
+ if (router) {
129
+ const tree = router.parseUrl(router.url);
130
+ const { [SPARK_EXTERNAL_LOGIN_QUERY_PARAM]: _, ...rest } = tree.queryParams;
131
+ tree.queryParams = rest;
132
+ return router.serializeUrl(tree);
133
+ }
134
+ const params = new URLSearchParams(window.location.search);
135
+ params.delete(SPARK_EXTERNAL_LOGIN_QUERY_PARAM);
136
+ const query = params.toString();
137
+ return window.location.pathname + (query ? `?${query}` : "");
138
+ }
139
+ leaveForProvider(url) {
140
+ window.location.assign(url);
141
+ }
142
+ externalFlow(path, provider, options) {
143
+ const returnUrl = options.returnUrl ?? this.config.defaultRedirectUrl;
144
+ const mode = options.mode ?? resolveSparkExternalLoginMode(this.config.externalLoginMode);
145
+ const url = `${this.config.apiBasePath}${path}?provider=${encodeURIComponent(provider)}&returnUrl=${encodeURIComponent(returnUrl)}`;
146
+ this.activeExternalAttempt?.("popup_closed");
147
+ if (mode === "redirect") {
148
+ const errorUrl = options.errorUrl ?? this.currentUrlForExternalLoginError();
149
+ this.leaveForProvider(`${url}&errorUrl=${encodeURIComponent(errorUrl)}`);
150
+ return new Promise(() => {});
151
+ }
152
+ const nonce = sparkExternalLoginNonce();
153
+ const popup = window.open(`${url}&popup=1&nonce=${nonce}&ngsw-bypass=true`, "spark-external-login", "width=600,height=700");
154
+ if (!popup) return Promise.resolve({
155
+ success: false,
156
+ error: "popup_blocked"
157
+ });
158
+ return new Promise((resolve) => {
159
+ const storageKey = sparkExternalLoginStorageKey(nonce);
160
+ const doneKey = sparkExternalLoginDoneKey(nonce);
161
+ let channel = null;
162
+ try {
163
+ channel = typeof BroadcastChannel === "function" ? new BroadcastChannel(SPARK_EXTERNAL_LOGIN_CHANNEL) : null;
164
+ } catch {
165
+ channel = null;
166
+ }
167
+ let settled = false;
168
+ const settle = (error) => {
169
+ if (settled) return;
170
+ settled = true;
171
+ window.removeEventListener("message", onMessage);
172
+ window.removeEventListener("storage", onStorage);
173
+ window.removeEventListener("focus", reread);
174
+ document.removeEventListener("visibilitychange", onVisibility);
175
+ if (channel) {
176
+ channel.removeEventListener("message", onChannel);
177
+ channel.close();
178
+ }
179
+ clearInterval(poll);
180
+ clearTimeout(timeout);
181
+ if (this.activeExternalAttempt === settle) {
182
+ this.activeExternalAttempt = null;
183
+ this.pendingExternalLogin.set(false);
184
+ }
185
+ this.zone.run(async () => {
186
+ if (!error) {
187
+ await this.csrfRefresh().catch(() => void 0);
188
+ await this.checkAuth();
189
+ }
190
+ resolve(error ? {
191
+ success: false,
192
+ error
193
+ } : { success: true });
194
+ });
195
+ };
196
+ const deliver = (data) => {
197
+ if (settled) return;
198
+ const message = data;
199
+ if (!message || typeof message !== "object") return;
200
+ if (message.type !== SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE) return;
201
+ if (message.nonce !== nonce) return;
202
+ const ack = {
203
+ type: SPARK_EXTERNAL_LOGIN_ACK_TYPE,
204
+ nonce
205
+ };
206
+ try {
207
+ channel?.postMessage(ack);
208
+ } catch {}
209
+ try {
210
+ localStorage.setItem(doneKey, "1");
211
+ localStorage.removeItem(storageKey);
212
+ } catch {}
213
+ try {
214
+ popup.close();
215
+ } catch {}
216
+ settle(message.success ? void 0 : message.error ?? "no_login_info");
217
+ };
218
+ const onMessage = (event) => {
219
+ if (event.origin !== window.location.origin) return;
220
+ deliver(event.data);
221
+ };
222
+ const onChannel = (event) => deliver(event.data);
223
+ const readStored = () => {
224
+ let stored = null;
225
+ try {
226
+ stored = localStorage.getItem(storageKey);
227
+ } catch {
228
+ return;
229
+ }
230
+ if (!stored) return;
231
+ try {
232
+ deliver(JSON.parse(stored));
233
+ } catch {}
234
+ };
235
+ const onStorage = (event) => {
236
+ if (event.key === storageKey && event.newValue) readStored();
237
+ };
238
+ const reread = () => readStored();
239
+ const onVisibility = () => {
240
+ if (document.visibilityState === "visible") readStored();
241
+ };
242
+ const poll = setInterval(() => {
243
+ let closed = false;
244
+ try {
245
+ closed = popup.closed;
246
+ } catch {
247
+ closed = false;
248
+ }
249
+ if (!closed) return;
250
+ clearInterval(poll);
251
+ if (this.activeExternalAttempt === settle) this.pendingExternalLogin.set(false);
252
+ }, POPUP_CLOSE_POLL_MS);
253
+ const timeout = setTimeout(() => settle("popup_closed"), SPARK_EXTERNAL_LOGIN_TIMEOUT_MS);
254
+ window.addEventListener("message", onMessage);
255
+ window.addEventListener("storage", onStorage);
256
+ window.addEventListener("focus", reread);
257
+ document.addEventListener("visibilitychange", onVisibility);
258
+ channel?.addEventListener("message", onChannel);
259
+ this.activeExternalAttempt = settle;
260
+ this.pendingExternalLogin.set(true);
261
+ });
262
+ }
263
+ async forgotPassword(email) {
264
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/forgotPassword`, { email }));
265
+ }
266
+ async resetPassword(email, resetCode, newPassword) {
267
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/resetPassword`, {
268
+ email,
269
+ resetCode,
270
+ newPassword
271
+ }));
272
+ }
273
+ confirmEmail(userId, code, changedEmail) {
274
+ return this.accountCall(async () => {
275
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/confirm-email`, {
276
+ userId,
277
+ code,
278
+ ...changedEmail ? { changedEmail } : {}
279
+ }));
280
+ if (this.isAuthenticated()) await this.checkAuth();
281
+ });
282
+ }
283
+ setPassword(newPassword, currentPassword) {
284
+ return this.accountCall(async () => {
285
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/manage/password`, {
286
+ newPassword,
287
+ ...currentPassword ? { currentPassword } : {}
288
+ }));
289
+ });
290
+ }
291
+ accountInfo() {
292
+ return this.accountCall(() => firstValueFrom(this.http.get(`${this.config.apiBasePath}/manage/info`)));
293
+ }
294
+ changeEmail(newEmail) {
295
+ return this.accountCall(() => firstValueFrom(this.http.post(`${this.config.apiBasePath}/manage/info`, { newEmail })));
296
+ }
297
+ profile() {
298
+ return this.accountCall(() => firstValueFrom(this.http.get(`${this.config.apiBasePath}/manage/profile`)));
299
+ }
300
+ updateProfile(update) {
301
+ return this.accountCall(async () => {
302
+ const saved = await firstValueFrom(this.http.post(`${this.config.apiBasePath}/manage/profile`, update));
303
+ if (update.userName !== void 0) await this.checkAuth();
304
+ return saved;
305
+ });
306
+ }
307
+ twoFactor(request = {}) {
308
+ return this.accountCall(() => firstValueFrom(this.http.post(`${this.config.apiBasePath}/manage/2fa`, request)));
309
+ }
310
+ authenticatorUri() {
311
+ return this.accountCall(() => firstValueFrom(this.http.get(`${this.config.apiBasePath}/manage/2fa/authenticator-uri`)));
312
+ }
313
+ externalLoginTwoFactor() {
314
+ return this.accountCall(() => firstValueFrom(this.http.get(`${this.config.apiBasePath}/manage/external-login-two-factor`)));
315
+ }
316
+ setExternalLoginTwoFactor(bypass, code) {
317
+ return this.accountCall(() => firstValueFrom(this.http.post(`${this.config.apiBasePath}/manage/external-login-two-factor`, bypass && code ? {
318
+ bypass,
319
+ code
320
+ } : { bypass })));
321
+ }
322
+ personalData() {
323
+ return this.accountCall(() => firstValueFrom(this.http.get(`${this.config.apiBasePath}/manage/personal-data`)));
324
+ }
325
+ deleteAccount(password) {
326
+ return this.accountCall(async () => {
327
+ await firstValueFrom(this.http.delete(`${this.config.apiBasePath}/manage/account`, { body: password ? { password } : {} }));
328
+ this.currentUser.set(null);
329
+ await this.csrfRefresh().catch(() => void 0);
330
+ });
331
+ }
332
+ async accountCall(call) {
333
+ try {
334
+ return {
335
+ success: true,
336
+ value: await call()
337
+ };
338
+ } catch (response) {
339
+ const http = response;
340
+ const body = http?.error ?? null;
341
+ const errors = {};
342
+ if (body && typeof body === "object" && body.errors && typeof body.errors === "object") for (const [key, value] of Object.entries(body.errors)) errors[key] = Array.isArray(value) ? value : [String(value)];
343
+ return {
344
+ success: false,
345
+ status: http?.status,
346
+ error: body && typeof body === "object" && typeof body.error === "string" ? body.error : void 0,
347
+ errors: Object.keys(errors).length ? errors : void 0
348
+ };
349
+ }
350
+ }
351
+ async signInWithPasskey() {
352
+ if (!passkeysSupported()) return {
353
+ success: false,
354
+ error: "unsupported"
355
+ };
356
+ try {
357
+ const optionsJson = await firstValueFrom(this.http.post(`${this.config.apiBasePath}/passkeys/request-options`, {}, { responseType: "text" }));
358
+ const options = PublicKeyCredential.parseRequestOptionsFromJSON(JSON.parse(optionsJson));
359
+ const credential = await navigator.credentials.get({ publicKey: options });
360
+ if (!credential) return {
361
+ success: false,
362
+ error: "no_credential"
363
+ };
364
+ await firstValueFrom(this.http.post(`${this.config.apiBasePath}/passkeys/sign-in`, { credentialJson: JSON.stringify(credential) }));
365
+ await this.csrfRefresh();
366
+ await this.checkAuth();
367
+ return { success: true };
368
+ } catch (error) {
369
+ return {
370
+ success: false,
371
+ error: this.passkeyError(error)
372
+ };
373
+ }
374
+ }
375
+ passkeyError(error) {
376
+ return sparkPasskeyError(error);
377
+ }
378
+ static ɵfac = i0.ɵɵngDeclareFactory({
379
+ minVersion: "12.0.0",
380
+ version: "22.2.0",
381
+ ngImport: i0,
382
+ type: SparkAuthService,
383
+ deps: [],
384
+ target: i0.ɵɵFactoryTarget.Injectable
385
+ });
386
+ static ɵprov = i0.ɵɵngDeclareInjectable({
387
+ minVersion: "12.0.0",
388
+ version: "22.2.0",
389
+ ngImport: i0,
390
+ type: SparkAuthService,
391
+ providedIn: "root"
392
+ });
393
+ };
394
+ i0.ɵɵngDeclareClassMetadata({
395
+ minVersion: "12.0.0",
396
+ version: "22.2.0",
397
+ ngImport: i0,
398
+ type: SparkAuthService,
399
+ decorators: [{
400
+ type: Injectable,
401
+ args: [{ providedIn: "root" }]
402
+ }],
403
+ ctorParameters: () => []
404
+ });
405
+ const currentLanguage = globalThis.__sparkCurrentLanguage ?? signal("en");
406
+ var SparkAuthTranslationService = class SparkAuthTranslationService {
407
+ http = inject(HttpClient);
408
+ translationsMap = signal({}, ...ngDevMode ? [{ debugName: "translationsMap" }] : /* istanbul ignore next */ []);
409
+ constructor() {
410
+ this.loadTranslations();
411
+ }
412
+ async loadTranslations() {
413
+ try {
414
+ const t = await firstValueFrom(this.http.get("/spark/translations"));
415
+ this.translationsMap.set(t);
416
+ } catch {}
417
+ }
418
+ t(key) {
419
+ const ts = this.translationsMap()[key];
420
+ if (!ts) return key;
421
+ return ts[currentLanguage()] ?? ts["en"] ?? Object.values(ts)[0] ?? key;
422
+ }
423
+ static ɵfac = i0.ɵɵngDeclareFactory({
424
+ minVersion: "12.0.0",
425
+ version: "22.2.0",
426
+ ngImport: i0,
427
+ type: SparkAuthTranslationService,
428
+ deps: [],
429
+ target: i0.ɵɵFactoryTarget.Injectable
430
+ });
431
+ static ɵprov = i0.ɵɵngDeclareInjectable({
432
+ minVersion: "12.0.0",
433
+ version: "22.2.0",
434
+ ngImport: i0,
435
+ type: SparkAuthTranslationService,
436
+ providedIn: "root"
437
+ });
438
+ };
439
+ i0.ɵɵngDeclareClassMetadata({
440
+ minVersion: "12.0.0",
441
+ version: "22.2.0",
442
+ ngImport: i0,
443
+ type: SparkAuthTranslationService,
444
+ decorators: [{
445
+ type: Injectable,
446
+ args: [{ providedIn: "root" }]
447
+ }],
448
+ ctorParameters: () => []
449
+ });
450
+ export { SparkAuthService, SparkAuthTranslationService };
451
+
452
+ //# sourceMappingURL=mintplayer-ng-spark-auth-core.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mintplayer-ng-spark-auth-core.mjs","names":[],"sources":["../../auth/core/src/spark-auth.service.ts","../../auth/core/src/spark-auth-translation.service.ts"],"sourcesContent":["import { computed, inject, Injectable, Injector, NgZone, signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Router } from '@angular/router';\nimport { firstValueFrom } from 'rxjs';\nimport {\n AuthUser,\n SPARK_AUTH_CONFIG,\n SparkAuthCapabilities,\n SPARK_EXTERNAL_LOGIN_ACK_TYPE,\n SPARK_EXTERNAL_LOGIN_CHANNEL,\n SPARK_EXTERNAL_LOGIN_ERRORS,\n SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE,\n SPARK_EXTERNAL_LOGIN_QUERY_PARAM,\n SPARK_EXTERNAL_LOGIN_TIMEOUT_MS,\n SparkExternalLoginAck,\n SparkExternalLoginError,\n SparkExternalLoginMessage,\n SparkExternalLoginOptions,\n SparkExternalLoginResult,\n resolveSparkExternalLoginMode,\n sparkExternalLoginDoneKey,\n sparkExternalLoginNonce,\n sparkExternalLoginStorageKey,\n SparkExternalLogins,\n SparkPasskeyError,\n SparkPasskeyResult,\n passkeysSupported,\n sparkPasskeyError,\n SparkUnlinkResult,\n SparkAccountInfo,\n SparkAccountProfile,\n SparkAccountProfileUpdate,\n SparkAccountResult,\n SparkAuthenticatorUri,\n SparkExternalLoginTwoFactor,\n SparkTwoFactorRequest,\n SparkTwoFactorState,\n} from '@mintplayer/ng-spark/auth/models';\n\n/** How often the popup is checked for a manual close. */\nconst POPUP_CLOSE_POLL_MS = 400;\n\n@Injectable({ providedIn: 'root' })\nexport class SparkAuthService {\n private readonly http = inject(HttpClient);\n private readonly config = inject(SPARK_AUTH_CONFIG);\n private readonly zone = inject(NgZone);\n\n private readonly currentUser = signal<AuthUser | null>(null);\n\n readonly user = this.currentUser.asReadonly();\n readonly isAuthenticated = computed(() => this.currentUser()?.isAuthenticated === true);\n\n /** Resolved lazily: only {@link takeExternalLoginResult} needs the router. */\n private readonly injector = inject(Injector);\n\n private readonly pendingExternalLogin = signal(false);\n\n /**\n * `true` while a popup sign-in or link attempt is open and its popup has not been seen closed.\n *\n * ⚠️ Drive button and spinner state from this, **not** from the promise settling. When the\n * provider's login page sends COOP the popup reads closed long before its result arrives, so this\n * turns `false` early (the buttons re-enable) while the attempt keeps listening, and a late result\n * still settles it.\n */\n readonly externalLoginPending = this.pendingExternalLogin.asReadonly();\n\n /** Settles the open popup attempt; `null` when none is open. */\n private activeExternalAttempt: ((error?: SparkExternalLoginError) => void) | null = null;\n\n constructor() {\n this.checkAuth();\n }\n\n /**\n * The credentials of a sign-in that answered `RequiresTwoFactor`, held in memory only until the\n * second step. MapIdentityApi's `/login` is stateless per request: the 2FA step must repeat the\n * email and password next to the code (its `LoginRequest` requires both), or it answers 400.\n */\n private pendingTwoFactorLogin: { email: string; password: string } | null = null;\n\n /** Whether the pending sign-in (and its two-factor step) asked for a persistent cookie. */\n private pendingRememberMe = true;\n\n /**\n * The login URL. MapIdentityApi's `/login` issues a persistent cookie for `useCookies=true`\n * and a session cookie (no `Expires`) when `useSessionCookies=true` is added.\n */\n private loginUrl(rememberMe: boolean): string {\n return `${this.config.apiBasePath}/login?useCookies=true${rememberMe ? '' : '&useSessionCookies=true'}`;\n }\n\n /**\n * Signs in with a cookie. `rememberMe` (default `true`) makes the cookie persistent; `false`\n * makes it a session cookie that ends with the browser session.\n */\n async login(email: string, password: string, rememberMe = true): Promise<void> {\n this.pendingTwoFactorLogin = null;\n this.pendingRememberMe = rememberMe;\n try {\n await firstValueFrom(this.http.post<void>(this.loginUrl(rememberMe), { email, password }));\n } catch (err: any) {\n if (err?.status === 401 && err?.error?.detail === 'RequiresTwoFactor')\n this.pendingTwoFactorLogin = { email, password };\n throw err;\n }\n await this.csrfRefresh();\n await this.checkAuth();\n }\n\n async loginTwoFactor(twoFactorCode: string, twoFactorRecoveryCode?: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(this.loginUrl(this.pendingRememberMe), {\n ...this.pendingTwoFactorLogin,\n twoFactorCode,\n twoFactorRecoveryCode,\n }));\n this.pendingRememberMe = true;\n this.pendingTwoFactorLogin = null;\n await this.csrfRefresh();\n await this.checkAuth();\n }\n\n /** `userName` is the public handle other users see; it is required and may not contain `@`. */\n async register(email: string, password: string, userName: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/register`, { email, password, userName }));\n }\n\n async logout(): Promise<void> {\n this.pendingTwoFactorLogin = null;\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/logout`, {}));\n await this.csrfRefresh();\n this.currentUser.set(null);\n }\n\n /**\n * What sign-in methods this deployment actually offers.\n *\n * Read from the server rather than assumed from the client's own route configuration: the two are\n * configured independently, and a mismatch is otherwise invisible until a user hits it.\n *\n * Callers that ask while a request is already in flight share it: the login page and the\n * `<spark-external-login-buttons>` it hosts both ask on construction, and that is one request, not\n * two. Nothing is cached once it settles, so a later call still reads the server's current answer.\n */\n capabilities(): Promise<SparkAuthCapabilities> {\n return this.capabilitiesInFlight ??= firstValueFrom(\n this.http.get<SparkAuthCapabilities>(`${this.config.apiBasePath}/capabilities`))\n .finally(() => this.capabilitiesInFlight = null);\n }\n\n private capabilitiesInFlight: Promise<SparkAuthCapabilities> | null = null;\n\n async csrfRefresh(): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/csrf-refresh`, {}));\n }\n\n async checkAuth(): Promise<AuthUser | null> {\n try {\n const user = await firstValueFrom(this.http.get<AuthUser>(`${this.config.apiBasePath}/me`));\n this.currentUser.set(user);\n return user;\n } catch {\n this.currentUser.set(null);\n return null;\n }\n }\n\n /**\n * Signs in through an external provider (GitHub, Google, …) and leaves this service's\n * `user()` signal up to date, exactly as `login()` does.\n *\n * The whole handshake lives here: a per-attempt nonce, four ways for the result to come back\n * (`postMessage` from the opener-held popup, a BroadcastChannel, a `localStorage` entry and its\n * `storage` event, and a re-read of that entry when this tab is shown again), the acknowledgement\n * that lets the callback page close itself, teardown on every exit path, and re-reading the\n * session afterwards. Callers get an outcome, not a protocol.\n *\n * A popup attempt settles only when its result arrives, when another attempt starts (the old one\n * settles `popup_closed`), or after 10 minutes (`popup_closed`). A popup that *looks* closed does\n * not settle it: the provider's COOP header severs the handle while the user is still signing in.\n * ⚠️ Take button and spinner state from {@link externalLoginPending}, not from this promise.\n *\n * The mode is the call's `mode`, else the configured `externalLoginMode` (`'auto'`: redirect in an\n * installed web app, popup otherwise). In `'redirect'` mode the returned promise never settles:\n * this document is being replaced, and the outcome arrives as the next page load\n * ({@link takeExternalLoginResult}) rather than as a value.\n */\n loginWithProvider(provider: string, options: SparkExternalLoginOptions = {}): Promise<SparkExternalLoginResult> {\n return this.externalFlow('/external-login', provider, options);\n }\n\n /**\n * What external logins are attached to the signed-in account, and what else could be.\n *\n * Only served when the deployment allows linking at all; under `Disabled` the endpoint is not\n * mapped, so this rejects with a 404. That is deliberate on the server side — an account page\n * offering an action the deployment forbids is worse than one that is absent.\n */\n async externalLogins(): Promise<SparkExternalLogins> {\n return await firstValueFrom(\n this.http.get<SparkExternalLogins>(`${this.config.apiBasePath}/external-logins`));\n }\n\n /**\n * Attaches another provider to the account that is already signed in.\n *\n * The same handshake as {@link loginWithProvider}, against the endpoint that links rather than\n * the one that signs in — the difference that matters is on the server, where the challenge is\n * keyed on the current user so the identity coming back cannot land in somebody else's session.\n */\n linkProvider(provider: string, options: SparkExternalLoginOptions = {}): Promise<SparkExternalLoginResult> {\n return this.externalFlow('/external-logins/link', provider, options);\n }\n\n /**\n * Detaches a provider from the signed-in account.\n *\n * ⚠️ Resolves with `error: 'last_credential'` rather than throwing when the server refuses to\n * remove the account's last way in. It is an expected answer to a reasonable request, not a\n * fault — and it is the server's refusal that protects the user, not the `canUnlink` flag the\n * list hands out.\n */\n async unlinkProvider(provider: string, providerKey: string): Promise<SparkUnlinkResult> {\n const url = `${this.config.apiBasePath}/external-logins/unlink`\n + `?provider=${encodeURIComponent(provider)}`\n + `&providerKey=${encodeURIComponent(providerKey)}`;\n try {\n await firstValueFrom(this.http.post<{ unlinked: boolean }>(url, {}));\n await this.checkAuth();\n return { success: true };\n } catch (response: unknown) {\n const error = (response as { error?: { error?: SparkUnlinkResult['error'] } })?.error?.error;\n return { success: false, error: error ?? 'unlink_failed' };\n }\n }\n\n /**\n * Reads the `?sparkExternalLogin=<code>` a redirect-mode round trip came back with, and strips it\n * from the address bar (a `replaceUrl` navigation, so Back does not bring it back and a reload\n * does not show it twice).\n *\n * Answers `null` when the URL carries none. A value that is not a known\n * {@link SparkExternalLoginError} is answered as `no_login_info` rather than echoed, so a crafted\n * link cannot put text of its choosing on the page.\n *\n * Only failures carry the parameter, and they land on the `errorUrl` the attempt was started with\n * (by default the page that started it); a successful redirect lands on the `returnUrl` signed in.\n * The shipped `<spark-external-login-buttons>` (on the sign-in and login pages) and the account\n * pages call this on load. Each failure is answered once: a second call before the strip lands\n * answers `null`. An app that starts a redirect-mode\n * attempt from some other page calls it there (or in its shell) to show the failure.\n */\n takeExternalLoginResult(): SparkExternalLoginError | null {\n const router = this.injector.get(Router, null);\n if (!router) return null;\n const tree = router.parseUrl(router.url);\n const raw = tree.queryParams[SPARK_EXTERNAL_LOGIN_QUERY_PARAM];\n if (raw === undefined) return null;\n\n // Taken once. Until the stripping navigation below lands, the URL still carries the parameter,\n // so a second component reading it in the same render would show the same failure twice.\n if (this.externalLoginResultTakenFrom === router.url) return null;\n const takenFrom = router.url;\n this.externalLoginResultTakenFrom = takenFrom;\n\n const { [SPARK_EXTERNAL_LOGIN_QUERY_PARAM]: _, ...rest } = tree.queryParams;\n tree.queryParams = rest;\n // Deferred: this is called from a component constructor, inside the navigation that is\n // activating it; starting another navigation synchronously there would cancel that one.\n queueMicrotask(() => void router.navigateByUrl(tree, { replaceUrl: true }).finally(() => {\n if (this.externalLoginResultTakenFrom === takenFrom) this.externalLoginResultTakenFrom = null;\n }));\n\n const code = Array.isArray(raw) ? raw[0] : raw;\n return SPARK_EXTERNAL_LOGIN_ERRORS.includes(code as SparkExternalLoginError)\n ? code as SparkExternalLoginError\n : 'no_login_info';\n }\n\n /** The URL whose `sparkExternalLogin` {@link takeExternalLoginResult} already took, until the strip lands. */\n private externalLoginResultTakenFrom: string | null = null;\n\n /**\n * The in-app URL of this page (path and query, as the router sees it) with any earlier\n * `sparkExternalLogin` removed, so a second failure does not stack onto the first one's code.\n * Falls back to `location` when no router is provided.\n */\n private currentUrlForExternalLoginError(): string {\n const router = this.injector.get(Router, null);\n if (router) {\n const tree = router.parseUrl(router.url);\n const { [SPARK_EXTERNAL_LOGIN_QUERY_PARAM]: _, ...rest } = tree.queryParams;\n tree.queryParams = rest;\n return router.serializeUrl(tree);\n }\n const params = new URLSearchParams(window.location.search);\n params.delete(SPARK_EXTERNAL_LOGIN_QUERY_PARAM);\n const query = params.toString();\n return window.location.pathname + (query ? `?${query}` : '');\n }\n\n /**\n * The full-page navigation of a redirect-mode attempt. Its own method only so a unit test can\n * observe the URL: jsdom locks `window.location.assign` against redefinition.\n */\n private leaveForProvider(url: string): void {\n window.location.assign(url);\n }\n\n private externalFlow(\n path: string,\n provider: string,\n options: SparkExternalLoginOptions,\n ): Promise<SparkExternalLoginResult> {\n const returnUrl = options.returnUrl ?? this.config.defaultRedirectUrl;\n const mode = options.mode ?? resolveSparkExternalLoginMode(this.config.externalLoginMode);\n const url = `${this.config.apiBasePath}${path}?provider=${encodeURIComponent(provider)}`\n + `&returnUrl=${encodeURIComponent(returnUrl)}`;\n\n // D2: a new attempt supersedes the open one, which settles 'popup_closed' and is torn down.\n this.activeExternalAttempt?.('popup_closed');\n\n if (mode === 'redirect') {\n // D9: a failure comes back to the page that started the attempt, which reads ?sparkExternalLogin.\n const errorUrl = options.errorUrl ?? this.currentUrlForExternalLoginError();\n this.leaveForProvider(`${url}&errorUrl=${encodeURIComponent(errorUrl)}`);\n return new Promise<SparkExternalLoginResult>(() => { /* the page is going away */ });\n }\n\n const nonce = sparkExternalLoginNonce();\n const popup = window.open(\n `${url}&popup=1&nonce=${nonce}&ngsw-bypass=true`, 'spark-external-login', 'width=600,height=700');\n // No automatic redirect fallback (F10): in Firefox's installed web app a null handle may still\n // have started the flow inside the app, and redirecting too would run a second one. The shipped\n // sign-in page offers \"Continue in this tab\" instead.\n if (!popup) return Promise.resolve({ success: false, error: 'popup_blocked' });\n\n return new Promise<SparkExternalLoginResult>((resolve) => {\n const storageKey = sparkExternalLoginStorageKey(nonce);\n const doneKey = sparkExternalLoginDoneKey(nonce);\n\n let channel: BroadcastChannel | null = null;\n try {\n channel = typeof BroadcastChannel === 'function' ? new BroadcastChannel(SPARK_EXTERNAL_LOGIN_CHANNEL) : null;\n } catch {\n channel = null;\n }\n\n // One settle path, so no listener, channel or timer outlives the attempt whichever way it ends.\n let settled = false;\n const settle = (error?: SparkExternalLoginError) => {\n if (settled) return;\n settled = true;\n window.removeEventListener('message', onMessage);\n window.removeEventListener('storage', onStorage);\n window.removeEventListener('focus', reread);\n document.removeEventListener('visibilitychange', onVisibility);\n if (channel) {\n channel.removeEventListener('message', onChannel);\n channel.close();\n }\n clearInterval(poll);\n clearTimeout(timeout);\n if (this.activeExternalAttempt === settle) {\n this.activeExternalAttempt = null;\n this.pendingExternalLogin.set(false);\n }\n\n this.zone.run(async () => {\n if (!error) {\n // Explicit, like login(): the identity just changed, so ask for a token bound to it\n // rather than relying on the server minting one on whatever response comes next.\n // Never fatal — the sign-in already happened, and a rejection here would leave this\n // promise unresolved.\n await this.csrfRefresh().catch(() => undefined);\n await this.checkAuth();\n }\n resolve(error ? { success: false, error } : { success: true });\n });\n };\n\n /** The first payload for this nonce wins, from whichever of the four sources brings it. */\n const deliver = (data: unknown) => {\n if (settled) return;\n const message = data as Partial<SparkExternalLoginMessage> | null;\n if (!message || typeof message !== 'object') return;\n if (message.type !== SPARK_EXTERNAL_LOGIN_MESSAGE_TYPE) return;\n // A payload for another attempt (another tab, an older popup) is not ours.\n if (message.nonce !== nonce) return;\n\n const ack: SparkExternalLoginAck = { type: SPARK_EXTERNAL_LOGIN_ACK_TYPE, nonce };\n try { channel?.postMessage(ack); } catch { /* the callback page falls back to its own close */ }\n try {\n localStorage.setItem(doneKey, '1');\n localStorage.removeItem(storageKey);\n } catch { /* storage may be unavailable (private mode); the result is already in hand */ }\n try { popup.close(); } catch { /* cut off by COOP: nothing to close from here */ }\n\n settle(message.success ? undefined : (message.error ?? 'no_login_info'));\n };\n\n const onMessage = (event: MessageEvent) => {\n if (event.origin !== window.location.origin) return;\n deliver(event.data);\n };\n const onChannel = (event: MessageEvent) => deliver(event.data);\n const readStored = () => {\n let stored: string | null = null;\n try { stored = localStorage.getItem(storageKey); } catch { return; }\n if (!stored) return;\n try { deliver(JSON.parse(stored)); } catch { /* not a payload */ }\n };\n const onStorage = (event: StorageEvent) => {\n if (event.key === storageKey && event.newValue) readStored();\n };\n // A frozen background tab may have missed both the channel and the storage event.\n const reread = () => readStored();\n const onVisibility = () => {\n if (document.visibilityState === 'visible') readStored();\n };\n\n // D2 final: the poll drives UI only. Under COOP (F6) the popup reads closed while the user is\n // still signing in, so a closed popup only re-enables the buttons; the listeners stay.\n const poll = setInterval(() => {\n let closed = false;\n try { closed = popup.closed; } catch { closed = false; }\n if (!closed) return;\n clearInterval(poll);\n if (this.activeExternalAttempt === settle) this.pendingExternalLogin.set(false);\n }, POPUP_CLOSE_POLL_MS);\n const timeout = setTimeout(() => settle('popup_closed'), SPARK_EXTERNAL_LOGIN_TIMEOUT_MS);\n\n window.addEventListener('message', onMessage);\n window.addEventListener('storage', onStorage);\n window.addEventListener('focus', reread);\n document.addEventListener('visibilitychange', onVisibility);\n channel?.addEventListener('message', onChannel);\n\n this.activeExternalAttempt = settle;\n this.pendingExternalLogin.set(true);\n });\n }\n\n async forgotPassword(email: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/forgotPassword`, { email }));\n }\n\n async resetPassword(email: string, resetCode: string, newPassword: string): Promise<void> {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/resetPassword`, {\n email,\n resetCode,\n newPassword,\n }));\n }\n\n // ---------------------------------------------------------------------------------------------\n // Account management (#460, D16) — the endpoints behind withAccount()'s pages. Every call resolves\n // a SparkAccountResult instead of throwing: a refusal (a wrong password, an invalid culture, a\n // missing re-authentication) is an expected answer the page renders, not a fault.\n // ---------------------------------------------------------------------------------------------\n\n /** `POST /confirm-email` — the query of a mailed confirmation link (a changed email too). */\n confirmEmail(userId: string, code: string, changedEmail?: string | null): Promise<SparkAccountResult> {\n return this.accountCall(async () => {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/confirm-email`, {\n userId, code, ...(changedEmail ? { changedEmail } : {}),\n }));\n // A signed-in user's claims carry the email; re-read them after a change.\n if (this.isAuthenticated()) await this.checkAuth();\n });\n }\n\n /** `POST /manage/password` — change it (`currentPassword` required when the account has one) or set a first one. */\n setPassword(newPassword: string, currentPassword?: string | null): Promise<SparkAccountResult> {\n return this.accountCall(async () => {\n await firstValueFrom(this.http.post<void>(`${this.config.apiBasePath}/manage/password`, {\n newPassword, ...(currentPassword ? { currentPassword } : {}),\n }));\n });\n }\n\n /** `GET /manage/info` — the email and whether it is confirmed. */\n accountInfo(): Promise<SparkAccountResult<SparkAccountInfo>> {\n return this.accountCall(() => firstValueFrom(this.http.get<SparkAccountInfo>(`${this.config.apiBasePath}/manage/info`)));\n }\n\n /**\n * `POST /manage/info { newEmail }` — mails a confirmation link to the NEW address; nothing changes\n * until it is followed (the confirm-email page completes it).\n */\n changeEmail(newEmail: string): Promise<SparkAccountResult<SparkAccountInfo>> {\n return this.accountCall(() =>\n firstValueFrom(this.http.post<SparkAccountInfo>(`${this.config.apiBasePath}/manage/info`, { newEmail })));\n }\n\n /** `GET /manage/profile`. */\n profile(): Promise<SparkAccountResult<SparkAccountProfile>> {\n return this.accountCall(() => firstValueFrom(this.http.get<SparkAccountProfile>(`${this.config.apiBasePath}/manage/profile`)));\n }\n\n /** `POST /manage/profile` — answers the saved profile, or field errors. */\n updateProfile(update: SparkAccountProfileUpdate): Promise<SparkAccountResult<SparkAccountProfile>> {\n return this.accountCall(async () => {\n const saved = await firstValueFrom(this.http.post<SparkAccountProfile>(`${this.config.apiBasePath}/manage/profile`, update));\n if (update.userName !== undefined) await this.checkAuth();\n return saved;\n });\n }\n\n /** `POST /manage/2fa` (Identity's) — an empty request reads the state and creates a key when none exists. */\n twoFactor(request: SparkTwoFactorRequest = {}): Promise<SparkAccountResult<SparkTwoFactorState>> {\n return this.accountCall(() =>\n firstValueFrom(this.http.post<SparkTwoFactorState>(`${this.config.apiBasePath}/manage/2fa`, request)));\n }\n\n /** `GET /manage/2fa/authenticator-uri` — 409 `no_authenticator_key` until {@link twoFactor} created one. */\n authenticatorUri(): Promise<SparkAccountResult<SparkAuthenticatorUri>> {\n return this.accountCall(() =>\n firstValueFrom(this.http.get<SparkAuthenticatorUri>(`${this.config.apiBasePath}/manage/2fa/authenticator-uri`)));\n }\n\n /** `GET /manage/external-login-two-factor` — whether an external sign-in skips the second factor. */\n externalLoginTwoFactor(): Promise<SparkAccountResult<SparkExternalLoginTwoFactor>> {\n return this.accountCall(() =>\n firstValueFrom(this.http.get<SparkExternalLoginTwoFactor>(`${this.config.apiBasePath}/manage/external-login-two-factor`)));\n }\n\n /**\n * `POST /manage/external-login-two-factor` — turning the bypass on needs a current authenticator\n * `code` (400 when it is not valid); turning it off needs none.\n */\n setExternalLoginTwoFactor(bypass: boolean, code?: string): Promise<SparkAccountResult<SparkExternalLoginTwoFactor>> {\n return this.accountCall(() =>\n firstValueFrom(this.http.post<SparkExternalLoginTwoFactor>(`${this.config.apiBasePath}/manage/external-login-two-factor`,\n bypass && code ? { bypass, code } : { bypass })));\n }\n\n /** `GET /manage/personal-data` — the account and every contributor's data, as JSON. */\n personalData(): Promise<SparkAccountResult<unknown>> {\n return this.accountCall(() => firstValueFrom(this.http.get<unknown>(`${this.config.apiBasePath}/manage/personal-data`)));\n }\n\n /**\n * `DELETE /manage/account` — re-authenticated by `password`, or by a sign-in younger than the server's\n * `ReauthenticationMaxAge` (5 minutes) when omitted. 403 `reauthentication_required` otherwise. On\n * success the session is gone.\n */\n deleteAccount(password?: string | null): Promise<SparkAccountResult> {\n return this.accountCall(async () => {\n await firstValueFrom(this.http.delete<void>(`${this.config.apiBasePath}/manage/account`, {\n body: password ? { password } : {},\n }));\n this.currentUser.set(null);\n // The antiforgery token was bound to the deleted identity.\n await this.csrfRefresh().catch(() => undefined);\n });\n }\n\n private async accountCall<T>(call: () => Promise<T>): Promise<SparkAccountResult<T>> {\n try {\n const value = await call();\n return { success: true, value };\n } catch (response: unknown) {\n const http = response as { status?: number; error?: unknown };\n const body = (http?.error ?? null) as { error?: unknown; errors?: Record<string, string[] | string> } | null;\n const errors: Record<string, string[]> = {};\n if (body && typeof body === 'object' && body.errors && typeof body.errors === 'object') {\n for (const [key, value] of Object.entries(body.errors)) errors[key] = Array.isArray(value) ? value : [String(value)];\n }\n return {\n success: false,\n status: http?.status,\n error: body && typeof body === 'object' && typeof body.error === 'string' ? body.error : undefined,\n errors: Object.keys(errors).length ? errors : undefined,\n };\n }\n }\n\n // ---------------------------------------------------------------------------------------------\n // Passkeys (WebAuthn)\n //\n // Sign-in is two round trips: ask the server for options, hand them to the authenticator, post what\n // it produced back. The server keeps the challenge itself — nothing below carries it, and nothing\n // below may start to, because a client that can choose its own challenge can replay an assertion.\n //\n // Managing the signed-in user's passkeys is not here: it is the generic passkeys page\n // (`/po/passkeys/me`), whose Add action runs the enrollment ceremony through the `webauthn.create`\n // client method (`sparkAuthClientMethods`).\n // ---------------------------------------------------------------------------------------------\n\n /**\n * Signs in with a passkey.\n *\n * ⚠️ Takes no username, and must not grow one. The server's request-options endpoint refuses to\n * accept an identity precisely so that its response cannot reveal whether an account exists; the\n * browser offers whatever discoverable credential it holds, and the account is resolved from the\n * credential afterwards.\n */\n async signInWithPasskey(): Promise<SparkPasskeyResult> {\n if (!passkeysSupported()) return { success: false, error: 'unsupported' };\n\n try {\n const optionsJson = await firstValueFrom(\n this.http.post(`${this.config.apiBasePath}/passkeys/request-options`, {}, { responseType: 'text' }));\n\n const options = PublicKeyCredential.parseRequestOptionsFromJSON(JSON.parse(optionsJson));\n const credential = await navigator.credentials.get({ publicKey: options });\n if (!credential) return { success: false, error: 'no_credential' };\n\n await firstValueFrom(\n this.http.post<void>(`${this.config.apiBasePath}/passkeys/sign-in`, {\n credentialJson: JSON.stringify(credential),\n }));\n\n await this.csrfRefresh();\n await this.checkAuth();\n return { success: true };\n } catch (error) {\n return { success: false, error: this.passkeyError(error) };\n }\n }\n\n /** {@link sparkPasskeyError}; shared with the `webauthn.create` client method. */\n private passkeyError(error: unknown): SparkPasskeyError {\n return sparkPasskeyError(error);\n }\n}\n","import { Injectable, inject, signal, type Signal } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\n\ntype TranslatedString = Record<string, string>;\n\n/** Read the global language signal set by @mintplayer/ng-spark */\nconst currentLanguage: Signal<string> =\n (globalThis as any).__sparkCurrentLanguage ?? signal('en');\n\n@Injectable({ providedIn: 'root' })\nexport class SparkAuthTranslationService {\n private readonly http = inject(HttpClient);\n private readonly translationsMap = signal<Record<string, TranslatedString>>({});\n\n constructor() {\n this.loadTranslations();\n }\n\n private async loadTranslations(): Promise<void> {\n try {\n const t = await firstValueFrom(this.http.get<Record<string, TranslatedString>>('/spark/translations'));\n this.translationsMap.set(t);\n } catch {\n // Translations failed to load; keys will be shown as-is\n }\n }\n\n t(key: string): string {\n const ts = this.translationsMap()[key];\n if (!ts) return key;\n const lang = currentLanguage();\n return ts[lang] ?? ts['en'] ?? Object.values(ts)[0] ?? key;\n }\n}\n"],"mappings":"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"}