@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,389 @@
1
+ import * as i0 from "@angular/core";
2
+ import { ChangeDetectionStrategy, Component, inject, signal } from "@angular/core";
3
+ import { Color } from "@mintplayer/ng-bootstrap";
4
+ import { BsAlertComponent } from "@mintplayer/ng-bootstrap/alert";
5
+ import { BsBadgeComponent } from "@mintplayer/ng-bootstrap/badge";
6
+ import { BsCardComponent, BsCardHeaderComponent } from "@mintplayer/ng-bootstrap/card";
7
+ import { BsCheckboxComponent } from "@mintplayer/ng-bootstrap/checkbox";
8
+ import { BsFormComponent } from "@mintplayer/ng-bootstrap/form";
9
+ import { BsSpinnerComponent } from "@mintplayer/ng-bootstrap/spinner";
10
+ import { SparkIconComponent } from "@mintplayer/ng-spark/icon";
11
+ import { SparkIdentityProviderDatePipe, SparkIdentityProviderPortalService, SparkIdentityProviderTextPipe, injectIdentityProviderText } from "@mintplayer/ng-spark/identity-provider/core";
12
+ var SparkConnectedApplicationsComponent = class SparkConnectedApplicationsComponent {
13
+ text = injectIdentityProviderText();
14
+ portal = inject(SparkIdentityProviderPortalService);
15
+ colors = Color;
16
+ loading = signal(true, ...ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []);
17
+ busy = signal(false, ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
18
+ apps = signal([], ...ngDevMode ? [{ debugName: "apps" }] : /* istanbul ignore next */ []);
19
+ error = signal("", ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
20
+ notice = signal("", ...ngDevMode ? [{ debugName: "notice" }] : /* istanbul ignore next */ []);
21
+ unticked = signal({}, ...ngDevMode ? [{ debugName: "unticked" }] : /* istanbul ignore next */ []);
22
+ constructor() {
23
+ this.load();
24
+ }
25
+ async load() {
26
+ this.loading.set(true);
27
+ const result = await this.portal.connectedApplications();
28
+ if (result.ok) {
29
+ this.apps.set(result.value);
30
+ this.unticked.set({});
31
+ } else {
32
+ this.apps.set([]);
33
+ this.error.set(this.text("loadFailed"));
34
+ }
35
+ this.loading.set(false);
36
+ }
37
+ hasOptionalScopes(app) {
38
+ return app.scopes.some((scope) => !scope.required);
39
+ }
40
+ isUnticked(app, scope) {
41
+ return this.untickedOf(app).includes(scope.name);
42
+ }
43
+ untickedOf(app) {
44
+ return this.unticked()[app.applicationId] ?? [];
45
+ }
46
+ setTicked(app, scope, ticked) {
47
+ if (scope.required) return;
48
+ this.unticked.update((map) => {
49
+ const current = map[app.applicationId] ?? [];
50
+ const next = ticked ? current.filter((name) => name !== scope.name) : [.../* @__PURE__ */ new Set([...current, scope.name])];
51
+ return {
52
+ ...map,
53
+ [app.applicationId]: next
54
+ };
55
+ });
56
+ }
57
+ async withdrawUnticked(app) {
58
+ const scopes = this.untickedOf(app).filter((name) => app.scopes.some((s) => s.name === name && !s.required));
59
+ if (!scopes.length) return;
60
+ await this.withdraw(app, scopes);
61
+ }
62
+ async withdrawAll(app) {
63
+ if (!confirm(this.text("apps.confirmWithdrawAll", app.displayName))) return;
64
+ await this.withdraw(app, void 0);
65
+ }
66
+ async withdraw(app, scopes) {
67
+ this.busy.set(true);
68
+ this.error.set("");
69
+ this.notice.set("");
70
+ if ((await this.portal.withdraw({
71
+ applicationId: app.applicationId,
72
+ scopes
73
+ })).ok) {
74
+ this.notice.set(this.text("apps.withdrawn"));
75
+ await this.load();
76
+ } else this.error.set(this.text("apps.withdrawFailed"));
77
+ this.busy.set(false);
78
+ }
79
+ static ɵfac = i0.ɵɵngDeclareFactory({
80
+ minVersion: "12.0.0",
81
+ version: "22.2.0",
82
+ ngImport: i0,
83
+ type: SparkConnectedApplicationsComponent,
84
+ deps: [],
85
+ target: i0.ɵɵFactoryTarget.Component
86
+ });
87
+ static ɵcmp = i0.ɵɵngDeclareComponent({
88
+ minVersion: "17.0.0",
89
+ version: "22.2.0",
90
+ type: SparkConnectedApplicationsComponent,
91
+ isStandalone: true,
92
+ selector: "spark-connected-applications",
93
+ ngImport: i0,
94
+ template: `
95
+ <div class="d-flex justify-content-center">
96
+ <bs-card style="width: 100%; max-width: 760px;">
97
+ <bs-card-header><h3 class="mb-0">{{ 'apps.title' | idp }}</h3></bs-card-header>
98
+ <div class="p-4">
99
+ <p class="text-muted">{{ 'apps.intro' | idp }}</p>
100
+ @if (notice()) {
101
+ <bs-alert [type]="colors.success" class="mb-3 d-block spark-idp-notice">{{ notice() }}</bs-alert>
102
+ }
103
+ @if (error()) {
104
+ <bs-alert [type]="colors.danger" class="mb-3 d-block spark-idp-error">{{ error() }}</bs-alert>
105
+ }
106
+ @if (loading()) {
107
+ <div class="text-center"><bs-spinner /></div>
108
+ } @else {
109
+ @for (app of apps(); track app.applicationId) {
110
+ <section class="border rounded p-3 mb-3 spark-connected-app">
111
+ <div class="d-flex align-items-center gap-3 mb-2">
112
+ @if (app.logoUrl) {
113
+ <img [src]="app.logoUrl" alt="" width="48" height="48" class="rounded spark-connected-app-logo"
114
+ style="object-fit: contain;" referrerpolicy="no-referrer" />
115
+ }
116
+ <div class="text-break">
117
+ <h5 class="mb-0 spark-connected-app-name">{{ app.displayName }}</h5>
118
+ @if (app.publisher) {
119
+ <small class="text-muted spark-connected-app-publisher">{{ 'apps.by' | idp: app.publisher }}</small>
120
+ }
121
+ @if (app.homepageUrl) {
122
+ <a [href]="app.homepageUrl" target="_blank" rel="noopener noreferrer" class="ms-2 small">{{ 'apps.website' | idp }}</a>
123
+ }
124
+ </div>
125
+ </div>
126
+
127
+ <small class="text-muted d-block spark-connected-app-dates">
128
+ {{ 'apps.firstGranted' | idp: (app.firstGrantedAt | idpDate) }} ·
129
+ @if (app.lastUsedAt) { {{ 'apps.lastUsed' | idp: (app.lastUsedAt | idpDate) }} } @else { {{ 'apps.neverUsed' | idp }} }
130
+ </small>
131
+ @if (app.remembered) {
132
+ <small class="text-muted d-block">
133
+ @if (app.rememberedUntil) { {{ 'apps.rememberedUntil' | idp: (app.rememberedUntil | idpDate) }} } @else { {{ 'apps.remembered' | idp }} }
134
+ </small>
135
+ }
136
+
137
+ <h6 class="mt-3">{{ 'apps.scopes' | idp }}</h6>
138
+ @if (hasOptionalScopes(app)) {
139
+ <small class="text-muted d-block mb-2">{{ 'apps.scopesHint' | idp }}</small>
140
+ }
141
+ <bs-form>
142
+ @for (scope of app.scopes; track scope.name) {
143
+ <div class="mb-2 spark-connected-scope" [attr.data-scope]="scope.name">
144
+ @if (scope.required) {
145
+ <div class="d-flex align-items-start gap-2" [attr.title]="'apps.requiredHint' | idp">
146
+ <spark-icon name="lock" class="mt-1" />
147
+ <div>
148
+ <span>{{ scope.displayName }}</span>
149
+ <bs-badge [type]="colors.secondary" class="ms-2 spark-scope-required">{{ 'apps.required' | idp }}</bs-badge>
150
+ @if (scope.description) { <small class="text-muted d-block">{{ scope.description }}</small> }
151
+ </div>
152
+ </div>
153
+ } @else {
154
+ <bs-checkbox [type]="'checkbox'" [name]="app.applicationId + '|' + scope.name"
155
+ [isToggled]="!isUnticked(app, scope)" (isToggledChange)="setTicked(app, scope, $event === true)">
156
+ {{ scope.displayName }}
157
+ </bs-checkbox>
158
+ @if (scope.description) { <small class="text-muted d-block ms-4">{{ scope.description }}</small> }
159
+ }
160
+ </div>
161
+ }
162
+ </bs-form>
163
+
164
+ <div class="d-flex flex-wrap gap-2 mt-3">
165
+ @if (hasOptionalScopes(app)) {
166
+ <button type="button" class="btn btn-outline-danger btn-sm spark-withdraw-unticked"
167
+ [disabled]="busy() || untickedOf(app).length === 0" (click)="withdrawUnticked(app)">
168
+ {{ 'apps.withdrawUnticked' | idp }}
169
+ </button>
170
+ }
171
+ <button type="button" class="btn btn-danger btn-sm spark-withdraw-all" [disabled]="busy()" (click)="withdrawAll(app)">
172
+ {{ 'apps.withdrawAll' | idp }}
173
+ </button>
174
+ </div>
175
+ </section>
176
+ } @empty {
177
+ @if (!error()) {
178
+ <p class="text-muted spark-connected-apps-empty">{{ 'apps.none' | idp }}</p>
179
+ }
180
+ }
181
+ }
182
+ </div>
183
+ </bs-card>
184
+ </div>
185
+ `,
186
+ isInline: true,
187
+ dependencies: [
188
+ {
189
+ kind: "component",
190
+ type: BsAlertComponent,
191
+ selector: "bs-alert",
192
+ inputs: [
193
+ "type",
194
+ "announce",
195
+ "isVisible"
196
+ ],
197
+ outputs: ["isVisibleChange", "afterOpenedOrClosed"]
198
+ },
199
+ {
200
+ kind: "component",
201
+ type: BsBadgeComponent,
202
+ selector: "bs-badge",
203
+ inputs: [
204
+ "type",
205
+ "unit",
206
+ "decorative"
207
+ ]
208
+ },
209
+ {
210
+ kind: "component",
211
+ type: BsCardComponent,
212
+ selector: "bs-card",
213
+ inputs: ["color", "outline"]
214
+ },
215
+ {
216
+ kind: "component",
217
+ type: BsCardHeaderComponent,
218
+ selector: "bs-card-header",
219
+ inputs: ["color", "navStyle"]
220
+ },
221
+ {
222
+ kind: "component",
223
+ type: BsCheckboxComponent,
224
+ selector: "bs-checkbox",
225
+ inputs: [
226
+ "type",
227
+ "isToggled",
228
+ "indeterminate",
229
+ "name",
230
+ "value",
231
+ "group"
232
+ ],
233
+ outputs: ["isToggledChange", "indeterminateChange"]
234
+ },
235
+ {
236
+ kind: "component",
237
+ type: BsFormComponent,
238
+ selector: "bs-form",
239
+ inputs: ["action", "method"],
240
+ outputs: ["submitted"]
241
+ },
242
+ {
243
+ kind: "component",
244
+ type: BsSpinnerComponent,
245
+ selector: "bs-spinner",
246
+ inputs: ["type", "color"]
247
+ },
248
+ {
249
+ kind: "component",
250
+ type: SparkIconComponent,
251
+ selector: "spark-icon",
252
+ inputs: ["name"]
253
+ },
254
+ {
255
+ kind: "pipe",
256
+ type: SparkIdentityProviderTextPipe,
257
+ name: "idp"
258
+ },
259
+ {
260
+ kind: "pipe",
261
+ type: SparkIdentityProviderDatePipe,
262
+ name: "idpDate"
263
+ }
264
+ ],
265
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
266
+ });
267
+ };
268
+ i0.ɵɵngDeclareClassMetadata({
269
+ minVersion: "12.0.0",
270
+ version: "22.2.0",
271
+ ngImport: i0,
272
+ type: SparkConnectedApplicationsComponent,
273
+ decorators: [{
274
+ type: Component,
275
+ args: [{
276
+ selector: "spark-connected-applications",
277
+ standalone: true,
278
+ changeDetection: ChangeDetectionStrategy.OnPush,
279
+ imports: [
280
+ BsAlertComponent,
281
+ BsBadgeComponent,
282
+ BsCardComponent,
283
+ BsCardHeaderComponent,
284
+ BsCheckboxComponent,
285
+ BsFormComponent,
286
+ BsSpinnerComponent,
287
+ SparkIconComponent,
288
+ SparkIdentityProviderTextPipe,
289
+ SparkIdentityProviderDatePipe
290
+ ],
291
+ template: `
292
+ <div class="d-flex justify-content-center">
293
+ <bs-card style="width: 100%; max-width: 760px;">
294
+ <bs-card-header><h3 class="mb-0">{{ 'apps.title' | idp }}</h3></bs-card-header>
295
+ <div class="p-4">
296
+ <p class="text-muted">{{ 'apps.intro' | idp }}</p>
297
+ @if (notice()) {
298
+ <bs-alert [type]="colors.success" class="mb-3 d-block spark-idp-notice">{{ notice() }}</bs-alert>
299
+ }
300
+ @if (error()) {
301
+ <bs-alert [type]="colors.danger" class="mb-3 d-block spark-idp-error">{{ error() }}</bs-alert>
302
+ }
303
+ @if (loading()) {
304
+ <div class="text-center"><bs-spinner /></div>
305
+ } @else {
306
+ @for (app of apps(); track app.applicationId) {
307
+ <section class="border rounded p-3 mb-3 spark-connected-app">
308
+ <div class="d-flex align-items-center gap-3 mb-2">
309
+ @if (app.logoUrl) {
310
+ <img [src]="app.logoUrl" alt="" width="48" height="48" class="rounded spark-connected-app-logo"
311
+ style="object-fit: contain;" referrerpolicy="no-referrer" />
312
+ }
313
+ <div class="text-break">
314
+ <h5 class="mb-0 spark-connected-app-name">{{ app.displayName }}</h5>
315
+ @if (app.publisher) {
316
+ <small class="text-muted spark-connected-app-publisher">{{ 'apps.by' | idp: app.publisher }}</small>
317
+ }
318
+ @if (app.homepageUrl) {
319
+ <a [href]="app.homepageUrl" target="_blank" rel="noopener noreferrer" class="ms-2 small">{{ 'apps.website' | idp }}</a>
320
+ }
321
+ </div>
322
+ </div>
323
+
324
+ <small class="text-muted d-block spark-connected-app-dates">
325
+ {{ 'apps.firstGranted' | idp: (app.firstGrantedAt | idpDate) }} ·
326
+ @if (app.lastUsedAt) { {{ 'apps.lastUsed' | idp: (app.lastUsedAt | idpDate) }} } @else { {{ 'apps.neverUsed' | idp }} }
327
+ </small>
328
+ @if (app.remembered) {
329
+ <small class="text-muted d-block">
330
+ @if (app.rememberedUntil) { {{ 'apps.rememberedUntil' | idp: (app.rememberedUntil | idpDate) }} } @else { {{ 'apps.remembered' | idp }} }
331
+ </small>
332
+ }
333
+
334
+ <h6 class="mt-3">{{ 'apps.scopes' | idp }}</h6>
335
+ @if (hasOptionalScopes(app)) {
336
+ <small class="text-muted d-block mb-2">{{ 'apps.scopesHint' | idp }}</small>
337
+ }
338
+ <bs-form>
339
+ @for (scope of app.scopes; track scope.name) {
340
+ <div class="mb-2 spark-connected-scope" [attr.data-scope]="scope.name">
341
+ @if (scope.required) {
342
+ <div class="d-flex align-items-start gap-2" [attr.title]="'apps.requiredHint' | idp">
343
+ <spark-icon name="lock" class="mt-1" />
344
+ <div>
345
+ <span>{{ scope.displayName }}</span>
346
+ <bs-badge [type]="colors.secondary" class="ms-2 spark-scope-required">{{ 'apps.required' | idp }}</bs-badge>
347
+ @if (scope.description) { <small class="text-muted d-block">{{ scope.description }}</small> }
348
+ </div>
349
+ </div>
350
+ } @else {
351
+ <bs-checkbox [type]="'checkbox'" [name]="app.applicationId + '|' + scope.name"
352
+ [isToggled]="!isUnticked(app, scope)" (isToggledChange)="setTicked(app, scope, $event === true)">
353
+ {{ scope.displayName }}
354
+ </bs-checkbox>
355
+ @if (scope.description) { <small class="text-muted d-block ms-4">{{ scope.description }}</small> }
356
+ }
357
+ </div>
358
+ }
359
+ </bs-form>
360
+
361
+ <div class="d-flex flex-wrap gap-2 mt-3">
362
+ @if (hasOptionalScopes(app)) {
363
+ <button type="button" class="btn btn-outline-danger btn-sm spark-withdraw-unticked"
364
+ [disabled]="busy() || untickedOf(app).length === 0" (click)="withdrawUnticked(app)">
365
+ {{ 'apps.withdrawUnticked' | idp }}
366
+ </button>
367
+ }
368
+ <button type="button" class="btn btn-danger btn-sm spark-withdraw-all" [disabled]="busy()" (click)="withdrawAll(app)">
369
+ {{ 'apps.withdrawAll' | idp }}
370
+ </button>
371
+ </div>
372
+ </section>
373
+ } @empty {
374
+ @if (!error()) {
375
+ <p class="text-muted spark-connected-apps-empty">{{ 'apps.none' | idp }}</p>
376
+ }
377
+ }
378
+ }
379
+ </div>
380
+ </bs-card>
381
+ </div>
382
+ `
383
+ }]
384
+ }],
385
+ ctorParameters: () => []
386
+ });
387
+ export { SparkConnectedApplicationsComponent };
388
+
389
+ //# sourceMappingURL=mintplayer-ng-spark-identity-provider-connected-applications.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mintplayer-ng-spark-identity-provider-connected-applications.mjs","names":[],"sources":["../../identity-provider/connected-applications/src/spark-connected-applications.component.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';\nimport { Color } from '@mintplayer/ng-bootstrap';\nimport { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';\nimport { BsBadgeComponent } from '@mintplayer/ng-bootstrap/badge';\nimport { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';\nimport { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';\nimport { BsFormComponent } from '@mintplayer/ng-bootstrap/form';\nimport { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';\nimport { SparkIconComponent } from '@mintplayer/ng-spark/icon';\nimport {\n SparkConnectedApplication,\n SparkConnectedScope,\n SparkIdentityProviderDatePipe,\n SparkIdentityProviderPortalService,\n SparkIdentityProviderTextPipe,\n injectIdentityProviderText,\n} from '@mintplayer/ng-spark/identity-provider/core';\n\n/**\n * The signed-in user's connected applications (`account/applications`, PRD D6/D7): each application\n * the user granted access to, with its logo, publisher, the granted scopes and the dates. The user\n * withdraws the whole grant, or unticks individual scopes and withdraws only those. Required scopes\n * (`openid`, and those the application or resource marks required) go only with the whole grant.\n *\n * The server resolves the scope texts in the request's language; the `spark-lang` cookie\n * `SparkLanguageService` writes is what makes that the language the user picked here.\n */\n@Component({\n selector: 'spark-connected-applications',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n BsAlertComponent, BsBadgeComponent, BsCardComponent, BsCardHeaderComponent, BsCheckboxComponent, BsFormComponent,\n BsSpinnerComponent, SparkIconComponent, SparkIdentityProviderTextPipe, SparkIdentityProviderDatePipe,\n ],\n template: `\n <div class=\"d-flex justify-content-center\">\n <bs-card style=\"width: 100%; max-width: 760px;\">\n <bs-card-header><h3 class=\"mb-0\">{{ 'apps.title' | idp }}</h3></bs-card-header>\n <div class=\"p-4\">\n <p class=\"text-muted\">{{ 'apps.intro' | idp }}</p>\n @if (notice()) {\n <bs-alert [type]=\"colors.success\" class=\"mb-3 d-block spark-idp-notice\">{{ notice() }}</bs-alert>\n }\n @if (error()) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3 d-block spark-idp-error\">{{ error() }}</bs-alert>\n }\n @if (loading()) {\n <div class=\"text-center\"><bs-spinner /></div>\n } @else {\n @for (app of apps(); track app.applicationId) {\n <section class=\"border rounded p-3 mb-3 spark-connected-app\">\n <div class=\"d-flex align-items-center gap-3 mb-2\">\n @if (app.logoUrl) {\n <img [src]=\"app.logoUrl\" alt=\"\" width=\"48\" height=\"48\" class=\"rounded spark-connected-app-logo\"\n style=\"object-fit: contain;\" referrerpolicy=\"no-referrer\" />\n }\n <div class=\"text-break\">\n <h5 class=\"mb-0 spark-connected-app-name\">{{ app.displayName }}</h5>\n @if (app.publisher) {\n <small class=\"text-muted spark-connected-app-publisher\">{{ 'apps.by' | idp: app.publisher }}</small>\n }\n @if (app.homepageUrl) {\n <a [href]=\"app.homepageUrl\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"ms-2 small\">{{ 'apps.website' | idp }}</a>\n }\n </div>\n </div>\n\n <small class=\"text-muted d-block spark-connected-app-dates\">\n {{ 'apps.firstGranted' | idp: (app.firstGrantedAt | idpDate) }} ·\n @if (app.lastUsedAt) { {{ 'apps.lastUsed' | idp: (app.lastUsedAt | idpDate) }} } @else { {{ 'apps.neverUsed' | idp }} }\n </small>\n @if (app.remembered) {\n <small class=\"text-muted d-block\">\n @if (app.rememberedUntil) { {{ 'apps.rememberedUntil' | idp: (app.rememberedUntil | idpDate) }} } @else { {{ 'apps.remembered' | idp }} }\n </small>\n }\n\n <h6 class=\"mt-3\">{{ 'apps.scopes' | idp }}</h6>\n @if (hasOptionalScopes(app)) {\n <small class=\"text-muted d-block mb-2\">{{ 'apps.scopesHint' | idp }}</small>\n }\n <bs-form>\n @for (scope of app.scopes; track scope.name) {\n <div class=\"mb-2 spark-connected-scope\" [attr.data-scope]=\"scope.name\">\n @if (scope.required) {\n <div class=\"d-flex align-items-start gap-2\" [attr.title]=\"'apps.requiredHint' | idp\">\n <spark-icon name=\"lock\" class=\"mt-1\" />\n <div>\n <span>{{ scope.displayName }}</span>\n <bs-badge [type]=\"colors.secondary\" class=\"ms-2 spark-scope-required\">{{ 'apps.required' | idp }}</bs-badge>\n @if (scope.description) { <small class=\"text-muted d-block\">{{ scope.description }}</small> }\n </div>\n </div>\n } @else {\n <bs-checkbox [type]=\"'checkbox'\" [name]=\"app.applicationId + '|' + scope.name\"\n [isToggled]=\"!isUnticked(app, scope)\" (isToggledChange)=\"setTicked(app, scope, $event === true)\">\n {{ scope.displayName }}\n </bs-checkbox>\n @if (scope.description) { <small class=\"text-muted d-block ms-4\">{{ scope.description }}</small> }\n }\n </div>\n }\n </bs-form>\n\n <div class=\"d-flex flex-wrap gap-2 mt-3\">\n @if (hasOptionalScopes(app)) {\n <button type=\"button\" class=\"btn btn-outline-danger btn-sm spark-withdraw-unticked\"\n [disabled]=\"busy() || untickedOf(app).length === 0\" (click)=\"withdrawUnticked(app)\">\n {{ 'apps.withdrawUnticked' | idp }}\n </button>\n }\n <button type=\"button\" class=\"btn btn-danger btn-sm spark-withdraw-all\" [disabled]=\"busy()\" (click)=\"withdrawAll(app)\">\n {{ 'apps.withdrawAll' | idp }}\n </button>\n </div>\n </section>\n } @empty {\n @if (!error()) {\n <p class=\"text-muted spark-connected-apps-empty\">{{ 'apps.none' | idp }}</p>\n }\n }\n }\n </div>\n </bs-card>\n </div>\n `,\n})\nexport class SparkConnectedApplicationsComponent {\n private readonly text = injectIdentityProviderText();\n private readonly portal = inject(SparkIdentityProviderPortalService);\n protected readonly colors = Color;\n\n readonly loading = signal(true);\n readonly busy = signal(false);\n readonly apps = signal<SparkConnectedApplication[]>([]);\n readonly error = signal('');\n readonly notice = signal('');\n /** Per application, the scopes the user unticked (to withdraw). */\n private readonly unticked = signal<Record<string, readonly string[]>>({});\n\n constructor() {\n void this.load();\n }\n\n async load(): Promise<void> {\n this.loading.set(true);\n const result = await this.portal.connectedApplications();\n if (result.ok) {\n this.apps.set(result.value);\n this.unticked.set({});\n } else {\n this.apps.set([]);\n this.error.set(this.text('loadFailed'));\n }\n this.loading.set(false);\n }\n\n protected hasOptionalScopes(app: SparkConnectedApplication): boolean {\n return app.scopes.some(scope => !scope.required);\n }\n\n protected isUnticked(app: SparkConnectedApplication, scope: SparkConnectedScope): boolean {\n return this.untickedOf(app).includes(scope.name);\n }\n\n protected untickedOf(app: SparkConnectedApplication): readonly string[] {\n return this.unticked()[app.applicationId] ?? [];\n }\n\n protected setTicked(app: SparkConnectedApplication, scope: SparkConnectedScope, ticked: boolean): void {\n if (scope.required) return;\n this.unticked.update(map => {\n const current = map[app.applicationId] ?? [];\n const next = ticked ? current.filter(name => name !== scope.name) : [...new Set([...current, scope.name])];\n return { ...map, [app.applicationId]: next };\n });\n }\n\n async withdrawUnticked(app: SparkConnectedApplication): Promise<void> {\n // Only optional scopes can be unticked; a required one is never sent on its own.\n const scopes = this.untickedOf(app).filter(name => app.scopes.some(s => s.name === name && !s.required));\n if (!scopes.length) return;\n await this.withdraw(app, scopes);\n }\n\n async withdrawAll(app: SparkConnectedApplication): Promise<void> {\n if (!confirm(this.text('apps.confirmWithdrawAll', app.displayName))) return;\n await this.withdraw(app, undefined);\n }\n\n private async withdraw(app: SparkConnectedApplication, scopes: string[] | undefined): Promise<void> {\n this.busy.set(true);\n this.error.set('');\n this.notice.set('');\n const result = await this.portal.withdraw({ applicationId: app.applicationId, scopes });\n if (result.ok) {\n this.notice.set(this.text('apps.withdrawn'));\n await this.load();\n } else {\n // The server's problem text is English only; the page says the same in the user's language.\n this.error.set(this.text('apps.withdrawFailed'));\n }\n this.busy.set(false);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,204 @@
1
+ import * as i0 from "@angular/core";
2
+ import { Injectable, Pipe, inject } from "@angular/core";
3
+ import { HttpClient, HttpErrorResponse } from "@angular/common/http";
4
+ import { firstValueFrom } from "rxjs";
5
+ import { currentLanguage } from "@mintplayer/ng-spark/models";
6
+ import { SparkAuthTranslationService } from "@mintplayer/ng-spark/auth/core";
7
+ const SPARK_IDENTITY_PROVIDER_API = "/spark/identity-provider";
8
+ var SparkIdentityProviderPortalService = class SparkIdentityProviderPortalService {
9
+ http = inject(HttpClient);
10
+ connectedApplications() {
11
+ return this.call(this.http.get(`${SPARK_IDENTITY_PROVIDER_API}/applications`));
12
+ }
13
+ withdraw(request) {
14
+ const body = request.scopes?.length ? {
15
+ applicationId: request.applicationId,
16
+ scopes: request.scopes
17
+ } : { applicationId: request.applicationId };
18
+ return this.call(this.http.post(`${SPARK_IDENTITY_PROVIDER_API}/applications/withdraw`, body));
19
+ }
20
+ developerStatus() {
21
+ return this.call(this.http.get(`${SPARK_IDENTITY_PROVIDER_API}/developer`));
22
+ }
23
+ requestDeveloperStatus(termsVersion) {
24
+ return this.call(this.http.post(`${SPARK_IDENTITY_PROVIDER_API}/developer`, { termsVersion }));
25
+ }
26
+ issueRegistrationToken() {
27
+ return this.call(this.http.post(`${SPARK_IDENTITY_PROVIDER_API}/developer/registration-token`, {}));
28
+ }
29
+ acceptInvitation(applicationId, token) {
30
+ return this.call(this.http.post(`${SPARK_IDENTITY_PROVIDER_API}/invitations/accept`, {
31
+ applicationId,
32
+ token
33
+ }));
34
+ }
35
+ signingKeys() {
36
+ return this.call(this.http.get(`${SPARK_IDENTITY_PROVIDER_API}/admin/keys`));
37
+ }
38
+ rotateSigningKeys() {
39
+ return this.call(this.http.post(`${SPARK_IDENTITY_PROVIDER_API}/admin/keys/rotate`, {}));
40
+ }
41
+ async call(request) {
42
+ try {
43
+ return {
44
+ ok: true,
45
+ value: await firstValueFrom(request)
46
+ };
47
+ } catch (error) {
48
+ if (error instanceof HttpErrorResponse) return {
49
+ ok: false,
50
+ status: error.status,
51
+ problem: problemText(error.error)
52
+ };
53
+ return {
54
+ ok: false,
55
+ status: 0,
56
+ problem: null
57
+ };
58
+ }
59
+ }
60
+ static ɵfac = i0.ɵɵngDeclareFactory({
61
+ minVersion: "12.0.0",
62
+ version: "22.2.0",
63
+ ngImport: i0,
64
+ type: SparkIdentityProviderPortalService,
65
+ deps: [],
66
+ target: i0.ɵɵFactoryTarget.Injectable
67
+ });
68
+ static ɵprov = i0.ɵɵngDeclareInjectable({
69
+ minVersion: "12.0.0",
70
+ version: "22.2.0",
71
+ ngImport: i0,
72
+ type: SparkIdentityProviderPortalService,
73
+ providedIn: "root"
74
+ });
75
+ };
76
+ i0.ɵɵngDeclareClassMetadata({
77
+ minVersion: "12.0.0",
78
+ version: "22.2.0",
79
+ ngImport: i0,
80
+ type: SparkIdentityProviderPortalService,
81
+ decorators: [{
82
+ type: Injectable,
83
+ args: [{ providedIn: "root" }]
84
+ }]
85
+ });
86
+ function problemText(body) {
87
+ if (body && typeof body === "object") {
88
+ const problem = body;
89
+ if (typeof problem.detail === "string" && problem.detail) return problem.detail;
90
+ }
91
+ return null;
92
+ }
93
+ const PREFIX = "identityProvider.spa.";
94
+ function format(template, args) {
95
+ return args.length ? template.replace(/\{(\d+)\}/g, (match, index) => {
96
+ const arg = args[Number(index)];
97
+ return arg === void 0 || arg === null ? match : String(arg);
98
+ }) : template;
99
+ }
100
+ function injectIdentityProviderText() {
101
+ const translations = inject(SparkAuthTranslationService);
102
+ return (key, ...args) => format(translations.t(PREFIX + key), args);
103
+ }
104
+ var SparkIdentityProviderTextPipe = class SparkIdentityProviderTextPipe {
105
+ translations = inject(SparkAuthTranslationService);
106
+ transform(key, ...args) {
107
+ return format(this.translations.t(PREFIX + key), args);
108
+ }
109
+ static ɵfac = i0.ɵɵngDeclareFactory({
110
+ minVersion: "12.0.0",
111
+ version: "22.2.0",
112
+ ngImport: i0,
113
+ type: SparkIdentityProviderTextPipe,
114
+ deps: [],
115
+ target: i0.ɵɵFactoryTarget.Pipe
116
+ });
117
+ static ɵpipe = i0.ɵɵngDeclarePipe({
118
+ minVersion: "14.0.0",
119
+ version: "22.2.0",
120
+ ngImport: i0,
121
+ type: SparkIdentityProviderTextPipe,
122
+ isStandalone: true,
123
+ name: "idp",
124
+ pure: false
125
+ });
126
+ };
127
+ i0.ɵɵngDeclareClassMetadata({
128
+ minVersion: "12.0.0",
129
+ version: "22.2.0",
130
+ ngImport: i0,
131
+ type: SparkIdentityProviderTextPipe,
132
+ decorators: [{
133
+ type: Pipe,
134
+ args: [{
135
+ name: "idp",
136
+ pure: false,
137
+ standalone: true
138
+ }]
139
+ }]
140
+ });
141
+ function identityProviderDate(value) {
142
+ if (!value) return "";
143
+ const date = new Date(value);
144
+ if (Number.isNaN(date.getTime())) return value;
145
+ try {
146
+ return new Intl.DateTimeFormat(currentLanguage(), {
147
+ dateStyle: "medium",
148
+ timeStyle: "short"
149
+ }).format(date);
150
+ } catch {
151
+ return date.toLocaleString();
152
+ }
153
+ }
154
+ var SparkIdentityProviderDatePipe = class SparkIdentityProviderDatePipe {
155
+ transform(value) {
156
+ return identityProviderDate(value);
157
+ }
158
+ static ɵfac = i0.ɵɵngDeclareFactory({
159
+ minVersion: "12.0.0",
160
+ version: "22.2.0",
161
+ ngImport: i0,
162
+ type: SparkIdentityProviderDatePipe,
163
+ deps: [],
164
+ target: i0.ɵɵFactoryTarget.Pipe
165
+ });
166
+ static ɵpipe = i0.ɵɵngDeclarePipe({
167
+ minVersion: "14.0.0",
168
+ version: "22.2.0",
169
+ ngImport: i0,
170
+ type: SparkIdentityProviderDatePipe,
171
+ isStandalone: true,
172
+ name: "idpDate",
173
+ pure: false
174
+ });
175
+ };
176
+ i0.ɵɵngDeclareClassMetadata({
177
+ minVersion: "12.0.0",
178
+ version: "22.2.0",
179
+ ngImport: i0,
180
+ type: SparkIdentityProviderDatePipe,
181
+ decorators: [{
182
+ type: Pipe,
183
+ args: [{
184
+ name: "idpDate",
185
+ pure: false,
186
+ standalone: true
187
+ }]
188
+ }]
189
+ });
190
+ const SPARK_IDENTITY_PROVIDER_QUERIES = {
191
+ applications: "oidc-applications",
192
+ resources: "oidc-resources",
193
+ developerRequests: "oidc-developer-requests",
194
+ scopeApprovals: "oidc-scope-approvals",
195
+ goLiveReviews: "oidc-golive-reviews",
196
+ grants: "oidc-grants",
197
+ auditEvents: "oidc-audit-events"
198
+ };
199
+ function identityProviderQueryLink(alias) {
200
+ return ["/query", alias];
201
+ }
202
+ export { SPARK_IDENTITY_PROVIDER_API, SPARK_IDENTITY_PROVIDER_QUERIES, SparkIdentityProviderDatePipe, SparkIdentityProviderPortalService, SparkIdentityProviderTextPipe, identityProviderDate, identityProviderQueryLink, injectIdentityProviderText };
203
+
204
+ //# sourceMappingURL=mintplayer-ng-spark-identity-provider-core.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mintplayer-ng-spark-identity-provider-core.mjs","names":[],"sources":["../../identity-provider/core/src/identity-provider-portal.service.ts","../../identity-provider/core/src/text.ts","../../identity-provider/core/src/queries.ts"],"sourcesContent":["import { Injectable, inject } from '@angular/core';\nimport { HttpClient, HttpErrorResponse } from '@angular/common/http';\nimport { firstValueFrom, type Observable } from 'rxjs';\nimport type {\n SparkConnectedApplication,\n SparkDeveloperStatus,\n SparkInvitationOutcome,\n SparkKeyRotation,\n SparkPortalResult,\n SparkRegistrationToken,\n SparkSigningKey,\n SparkWithdrawAccessRequest,\n} from './models';\n\n/** The portal API's prefix (`IdentityProviderPortalGroup.Prefix` on the server). */\nexport const SPARK_IDENTITY_PROVIDER_API = '/spark/identity-provider';\n\n/**\n * The identity provider's portal API for the SPA pages (PRD D7). Every call is for a signed-in user;\n * the POSTs require the antiforgery token, which Angular's XSRF support adds from the `XSRF-TOKEN`\n * cookie (`withSparkAuth()` configures it), so the calls are plain `HttpClient` requests.\n *\n * Calls never throw: each resolves to a {@link SparkPortalResult}, so a page branches on `ok` and on\n * the status (403 for a page the user may not see) instead of catching.\n */\n@Injectable({ providedIn: 'root' })\nexport class SparkIdentityProviderPortalService {\n private readonly http = inject(HttpClient);\n\n connectedApplications(): Promise<SparkPortalResult<SparkConnectedApplication[]>> {\n return this.call(this.http.get<SparkConnectedApplication[]>(`${SPARK_IDENTITY_PROVIDER_API}/applications`));\n }\n\n /** Withdraws the whole grant, or only `scopes` when given. Answers 204, or 409 when it could not. */\n withdraw(request: SparkWithdrawAccessRequest): Promise<SparkPortalResult<null>> {\n const body: SparkWithdrawAccessRequest = request.scopes?.length\n ? { applicationId: request.applicationId, scopes: request.scopes }\n : { applicationId: request.applicationId };\n return this.call(this.http.post<null>(`${SPARK_IDENTITY_PROVIDER_API}/applications/withdraw`, body));\n }\n\n developerStatus(): Promise<SparkPortalResult<SparkDeveloperStatus>> {\n return this.call(this.http.get<SparkDeveloperStatus>(`${SPARK_IDENTITY_PROVIDER_API}/developer`));\n }\n\n /** Accepts the terms version the page showed; the server refuses (409) a version that is no longer current. */\n requestDeveloperStatus(termsVersion: number): Promise<SparkPortalResult<SparkDeveloperStatus>> {\n return this.call(this.http.post<SparkDeveloperStatus>(`${SPARK_IDENTITY_PROVIDER_API}/developer`, { termsVersion }));\n }\n\n issueRegistrationToken(): Promise<SparkPortalResult<SparkRegistrationToken>> {\n return this.call(this.http.post<SparkRegistrationToken>(`${SPARK_IDENTITY_PROVIDER_API}/developer/registration-token`, {}));\n }\n\n acceptInvitation(applicationId: string, token: string): Promise<SparkPortalResult<SparkInvitationOutcome>> {\n return this.call(this.http.post<SparkInvitationOutcome>(`${SPARK_IDENTITY_PROVIDER_API}/invitations/accept`, { applicationId, token }));\n }\n\n signingKeys(): Promise<SparkPortalResult<SparkSigningKey[]>> {\n return this.call(this.http.get<SparkSigningKey[]>(`${SPARK_IDENTITY_PROVIDER_API}/admin/keys`));\n }\n\n rotateSigningKeys(): Promise<SparkPortalResult<SparkKeyRotation>> {\n return this.call(this.http.post<SparkKeyRotation>(`${SPARK_IDENTITY_PROVIDER_API}/admin/keys/rotate`, {}));\n }\n\n private async call<T>(request: Observable<T>): Promise<SparkPortalResult<T>> {\n try {\n return { ok: true, value: await firstValueFrom(request) };\n } catch (error) {\n if (error instanceof HttpErrorResponse) {\n return { ok: false, status: error.status, problem: problemText(error.error) };\n }\n return { ok: false, status: 0, problem: null };\n }\n }\n}\n\n/** The text of an RFC 7807 problem (`TypedResults.Problem(detail)`), when the body is one. */\nfunction problemText(body: unknown): string | null {\n if (body && typeof body === 'object') {\n const problem = body as { detail?: unknown; title?: unknown };\n if (typeof problem.detail === 'string' && problem.detail) return problem.detail;\n }\n return null;\n}\n","import { Pipe, type PipeTransform, inject } from '@angular/core';\nimport { currentLanguage } from '@mintplayer/ng-spark/models';\nimport { SparkAuthTranslationService } from '@mintplayer/ng-spark/auth/core';\n\n/** Where the pages' texts live: `identityProvider.spa.*` in the identity-provider library's `translations.json`. */\nconst PREFIX = 'identityProvider.spa.';\n\nfunction format(template: string, args: unknown[]): string {\n return args.length\n ? template.replace(/\\{(\\d+)\\}/g, (match, index: string) => {\n const arg = args[Number(index)];\n return arg === undefined || arg === null ? match : String(arg);\n })\n : template;\n}\n\n/**\n * A text function for component code: `private readonly text = injectIdentityProviderText();` then\n * `this.text('apps.withdrawn')`. The texts come from the server's translations in the app's current\n * language, like every other ng-spark page; `{0}`, `{1}` are replaced by the arguments.\n */\nexport function injectIdentityProviderText(): (key: string, ...args: unknown[]) => string {\n const translations = inject(SparkAuthTranslationService);\n return (key, ...args) => format(translations.t(PREFIX + key), args);\n}\n\n/** `{{ 'apps.title' | idp }}`, `{{ 'apps.by' | idp: publisher }}`. Impure: follows the language and the loaded translations. */\n@Pipe({ name: 'idp', pure: false, standalone: true })\nexport class SparkIdentityProviderTextPipe implements PipeTransform {\n private readonly translations = inject(SparkAuthTranslationService);\n\n transform(key: string, ...args: unknown[]): string {\n return format(this.translations.t(PREFIX + key), args);\n }\n}\n\n/**\n * A server date (ISO string) as a date and time in the app's current language; empty for null.\n * `Intl` rather than Angular's `DatePipe`, which needs locale data registered for every language.\n */\nexport function identityProviderDate(value: string | null | undefined): string {\n if (!value) return '';\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return value;\n try {\n return new Intl.DateTimeFormat(currentLanguage(), { dateStyle: 'medium', timeStyle: 'short' }).format(date);\n } catch {\n return date.toLocaleString();\n }\n}\n\n/** `{{ app.firstGrantedAt | idpDate }}`. Impure: follows the language signal. */\n@Pipe({ name: 'idpDate', pure: false, standalone: true })\nexport class SparkIdentityProviderDatePipe implements PipeTransform {\n transform(value: string | null | undefined): string {\n return identityProviderDate(value);\n }\n}\n","/**\n * The identity-provider library's queries, by the aliases in its `App_Data/Model/*.json`\n * (`queries[].alias`). They open in the generic query page of `sparkRoutes()`, `/query/:queryId`,\n * which resolves an alias as well as an id. Each lists only what the caller may see.\n */\nexport const SPARK_IDENTITY_PROVIDER_QUERIES = {\n /** `OidcApplication`: the caller's applications (members-only filter), or all of them for an administrator. */\n applications: 'oidc-applications',\n resources: 'oidc-resources',\n developerRequests: 'oidc-developer-requests',\n scopeApprovals: 'oidc-scope-approvals',\n goLiveReviews: 'oidc-golive-reviews',\n grants: 'oidc-grants',\n auditEvents: 'oidc-audit-events',\n} as const;\n\n/** The router commands that open one of {@link SPARK_IDENTITY_PROVIDER_QUERIES}. */\nexport function identityProviderQueryLink(alias: string): string[] {\n return ['/query', alias];\n}\n"],"mappings":"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"}