@mintplayer/ng-spark 22.30.0 → 22.32.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 (84) hide show
  1. package/README.md +2 -2
  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 +294 -2
  38. package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
  39. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +389 -0
  40. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs.map +1 -0
  41. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +204 -0
  42. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs.map +1 -0
  43. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +486 -0
  44. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs.map +1 -0
  45. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +350 -0
  46. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs.map +1 -0
  47. package/fesm2022/mintplayer-ng-spark-identity-provider.mjs +66 -0
  48. package/fesm2022/mintplayer-ng-spark-identity-provider.mjs.map +1 -0
  49. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +9 -4
  50. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  51. package/fesm2022/mintplayer-ng-spark-pwa.mjs +162 -0
  52. package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -0
  53. package/fesm2022/mintplayer-ng-spark-services.mjs +11 -1
  54. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  55. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  56. package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
  57. package/package.json +99 -1
  58. package/types/mintplayer-ng-spark-auth-account.d.ts +255 -0
  59. package/types/mintplayer-ng-spark-auth-auth-bar.d.ts +22 -0
  60. package/types/mintplayer-ng-spark-auth-confirm-email.d.ts +21 -0
  61. package/types/mintplayer-ng-spark-auth-core.d.ts +197 -0
  62. package/types/mintplayer-ng-spark-auth-external-login.d.ts +132 -0
  63. package/types/mintplayer-ng-spark-auth-forgot-password.d.ts +18 -0
  64. package/types/mintplayer-ng-spark-auth-guards.d.ts +15 -0
  65. package/types/mintplayer-ng-spark-auth-interceptors.d.ts +2 -0
  66. package/types/mintplayer-ng-spark-auth-login.d.ts +46 -0
  67. package/types/mintplayer-ng-spark-auth-models.d.ts +489 -0
  68. package/types/mintplayer-ng-spark-auth-pipes.d.ts +8 -0
  69. package/types/mintplayer-ng-spark-auth-register.d.ts +21 -0
  70. package/types/mintplayer-ng-spark-auth-reset-password.d.ts +25 -0
  71. package/types/mintplayer-ng-spark-auth-routes.d.ts +154 -0
  72. package/types/mintplayer-ng-spark-auth-sign-in.d.ts +84 -0
  73. package/types/mintplayer-ng-spark-auth-two-factor.d.ts +23 -0
  74. package/types/mintplayer-ng-spark-auth.d.ts +24 -0
  75. package/types/mintplayer-ng-spark-client-operations.d.ts +78 -3
  76. package/types/mintplayer-ng-spark-identity-provider-connected-applications.d.ts +35 -0
  77. package/types/mintplayer-ng-spark-identity-provider-core.d.ts +144 -0
  78. package/types/mintplayer-ng-spark-identity-provider-developers.d.ts +55 -0
  79. package/types/mintplayer-ng-spark-identity-provider-management.d.ts +33 -0
  80. package/types/mintplayer-ng-spark-identity-provider.d.ts +64 -0
  81. package/types/mintplayer-ng-spark-pwa.d.ts +89 -0
  82. package/types/mintplayer-ng-spark-services.d.ts +10 -0
  83. package/types/mintplayer-ng-spark-shell.d.ts +2 -2
  84. package/types/mintplayer-ng-spark.d.ts +4 -4
package/README.md CHANGED
@@ -6,7 +6,7 @@ attribute renderers, client operations, the program-units shell, and the UI halv
6
6
  packages (soft delete, history, moderation).
7
7
 
8
8
  Sign-in, registration and the account pages are in the companion package
9
- [`@mintplayer/ng-spark-auth`](https://github.com/MintPlayer/MintPlayer.Spark/blob/master/libs/node_packages/ng-spark-auth/README.md).
9
+ [`@mintplayer/ng-spark/auth`](https://github.com/MintPlayer/MintPlayer.Spark/blob/master/libs/node_packages/ng-spark/auth/README.md).
10
10
 
11
11
  ## Install
12
12
 
@@ -52,7 +52,7 @@ Import from the entry point, not from the package root; each one is a separate c
52
52
 
53
53
  | Entry point | What it provides |
54
54
  |---|---|
55
- | `@mintplayer/ng-spark` | `provideSpark(config?)`, `SPARK_CONFIG` (`baseUrl`), `SPARK_AUTH_STATE` (the bridge `ng-spark-auth` fills) |
55
+ | `@mintplayer/ng-spark` | `provideSpark(config?)`, `SPARK_CONFIG` (`baseUrl`), `SPARK_AUTH_STATE` (the bridge `ng-spark/auth` fills) |
56
56
  | `/routes` | `sparkRoutes()` — the routed persistent-object and query pages |
57
57
  | `/services` | `SparkService`, `SparkStreamingService`, `SparkLanguageService`, `SparkQueryActionsService`, `RetryActionService`; the timezone feature `withSparkTimezone()` |
58
58
  | `/models` | The wire types (persistent objects, attributes, queries, envelopes) |
package/auth/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # @mintplayer/ng-spark/auth
2
+
3
+ The Angular half of [`MintPlayer.Spark.Authorization`](https://github.com/MintPlayer/MintPlayer.Spark/blob/master/libs/authorization/MintPlayer.Spark.Authorization/README.md):
4
+ sign-in (email or user name, two-factor, external providers, passkeys), registration, password reset,
5
+ the account pages (`withAccount()`), route guards and the HTTP interceptor that carries the session
6
+ and the antiforgery token to `/spark/auth`.
7
+
8
+ The auth entry points of [`@mintplayer/ng-spark`](https://github.com/MintPlayer/MintPlayer.Spark/blob/master/libs/node_packages/ng-spark/README.md); they ship inside that package.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @mintplayer/ng-spark @mintplayer/ng-bootstrap
14
+ ```
15
+
16
+ The major version follows Angular's: `22.x` targets Angular 22. When the server references
17
+ `MintPlayer.Spark.Authorization`, the build warns with **SPARK030** if the SPA's `package.json` does
18
+ not declare `@mintplayer/ng-spark` (the generated `spark-auth.setup.ts` imports it).
19
+
20
+ ## Setup
21
+
22
+ ```ts
23
+ // app.config.ts
24
+ import { provideHttpClient } from '@angular/common/http';
25
+ import { provideSparkAuth, withSparkAuth } from '@mintplayer/ng-spark/auth';
26
+
27
+ export const appConfig: ApplicationConfig = {
28
+ providers: [
29
+ provideRouter(routes),
30
+ provideHttpClient(...withSparkAuth()), // interceptor + XSRF-TOKEN / X-XSRF-TOKEN
31
+ provideSparkAuth(), // optional: { apiBasePath, loginUrl, defaultRedirectUrl, externalLoginMode }
32
+ ],
33
+ };
34
+
35
+ // app.routes.ts
36
+ import { sparkAuthRoutes, withLocalLogin, withRegistration, withAccount,
37
+ withExternalLogin, githubProvider } from '@mintplayer/ng-spark/auth/routes';
38
+ import { sparkAuthGuard } from '@mintplayer/ng-spark/auth/guards';
39
+ import { sparkRoutes } from '@mintplayer/ng-spark/routes';
40
+
41
+ export const routes: Routes = [
42
+ ...sparkAuthRoutes(
43
+ withLocalLogin(), // login, two-factor, forgot/reset password
44
+ withRegistration(), // only with SparkLocalCredentials.Full on the server
45
+ withExternalLogin(githubProvider()), // sign-in page with a button per provider
46
+ withAccount(), // confirm-email + the account pages
47
+ ),
48
+ { path: 'orders', canActivate: [sparkAuthGuard], loadComponent: () => import('./orders.component') },
49
+ ...sparkRoutes(),
50
+ ];
51
+ ```
52
+
53
+ Mount only the features the server maps: see the route × mode table in
54
+ [guide-authentication-schemes.md](https://github.com/MintPlayer/MintPlayer.Spark/blob/master/docs/guide-authentication-schemes.md#choosing-how-much-of-the-local-credential-surface-to-mount).
55
+ The server-generated `spark-auth.setup.ts` wires the same calls for you
56
+ ([Authorization README § Angular Frontend Setup](https://github.com/MintPlayer/MintPlayer.Spark/blob/master/libs/authorization/MintPlayer.Spark.Authorization/README.md#angular-frontend-setup)).
57
+
58
+ ## `withAccount()`
59
+
60
+ Mounts the account pages; each is also a standalone component from `/account` (confirm-email from
61
+ `/confirm-email`). The passkeys path is no component: it forwards to the generic passkeys page.
62
+
63
+ | Page | Default path | Component |
64
+ |---|---|---|
65
+ | Confirm email (public; the mail link target) | `confirm-email` | `SparkConfirmEmailComponent` |
66
+ | Overview | `account` | `SparkAccountOverviewComponent` |
67
+ | Profile — user name, email change (only with the server's `SparkEmailChange.Enabled`), mail language, app fields | `account/profile` | `SparkAccountProfileComponent` |
68
+ | Change / set password | `account/password` | `SparkChangePasswordComponent` |
69
+ | Two-factor — authenticator (server-rendered QR), recovery codes | `account/two-factor` | `SparkTwoFactorSetupComponent` |
70
+ | Connected logins | `account/logins` | `SparkExternalLoginsComponent` |
71
+ | Passkeys — forwards to `/po/passkeys/me`, the Authorization library's `Passkeys` page (list, add, rename, remove) | `account/passkeys` | — |
72
+ | Personal data export + account deletion | `account/personal-data` | `SparkPersonalDataComponent` |
73
+
74
+ ```ts
75
+ withAccount({ exclude: ['externalLogins', 'passkeys'] }) // drop pages the server does not support
76
+ withAccount({ canActivate: [myGuard] }) // default: [sparkAuthGuard]
77
+ ```
78
+
79
+ The overview (`/account`) shows who is signed in and links each mounted page. Pages behind a
80
+ server switch are also checked against `GET /spark/auth/capabilities`: password and two-factor appear
81
+ only when `LocalCredentials` is not `Disabled`, passkeys only when the server reports `passkeys` and
82
+ the browser supports WebAuthn.
83
+
84
+ `<spark-auth-bar>` renders **Account** and **Logout** side by side in a `<bs-button-group>` when
85
+ `withAccount()` is mounted (the bar reads the mounted path from the router configuration, so a custom
86
+ `account` path is followed), and Logout alone otherwise. It no longer shows the user name or a passkey
87
+ button; both live on the account page.
88
+
89
+ `confirm-email` must stay at the server's `Spark:Auth:Links:ConfirmEmailPath` (default
90
+ `/confirm-email`). App-specific profile fields are declared with
91
+ `provideSparkAccountProfileFields(...)` (`/models`) and validated on the server by an
92
+ `ISparkProfileContributor<TUser>`.
93
+
94
+ ## External sign-in: popup, redirect and installed apps
95
+
96
+ `SparkAuthService.loginWithProvider(scheme, options?)` signs in through a provider;
97
+ `linkProvider(scheme, options?)` attaches one to the signed-in account. Both use one handshake.
98
+
99
+ **Mode.** `provideSparkAuth({ externalLoginMode })` sets the default, and a call's own
100
+ `{ mode: 'popup' | 'redirect' }` overrides it.
101
+
102
+ | `externalLoginMode` | Behaviour |
103
+ |---|---|
104
+ | `'auto'` (default) | A full-page **redirect** inside an installed web app (`display-mode: standalone`, or iOS's `navigator.standalone`), a **popup** in a browser tab |
105
+ | `'popup'` | Always a popup |
106
+ | `'redirect'` | Always a redirect |
107
+
108
+ Inside an installed app a popup is captured into the app, or opens in a custom tab with no usable
109
+ opener, so only a redirect is reliable there.
110
+
111
+ **Popup mode.** Each attempt sends a fresh nonce. The callback page hands the result back over four
112
+ channels, and the first one that brings this attempt's nonce wins:
113
+ - `postMessage` to the opener;
114
+ - the `spark:external-login` BroadcastChannel;
115
+ - `localStorage` (`spark:external-login:<nonce>`) and its `storage` event;
116
+ - a re-read of that key when the tab becomes visible or gains focus.
117
+
118
+ The opener then acknowledges, so the callback page closes itself. If `window.open` returns no window,
119
+ the result is `popup_blocked`. There is no automatic fallback: the shipped sign-in page shows the
120
+ error and offers **Continue in this tab**, which retries the same provider in redirect mode.
121
+
122
+ **When the promise settles.** A popup attempt settles only when one of these happens:
123
+ - its result arrives;
124
+ - another attempt starts (the old one settles `popup_closed`);
125
+ - 10 minutes pass (`popup_closed`).
126
+
127
+ A popup that *looks* closed does not settle it. Facebook, X and LinkedIn send
128
+ `Cross-Origin-Opener-Policy`, which cuts the opener's handle, so the popup reads `closed` while the
129
+ user is still typing their password. A late result still signs the user in.
130
+
131
+ > ⚠️ **Button and spinner state must come from `externalLoginPending`, not from the promise
132
+ > settling.** `SparkAuthService.externalLoginPending()` is a signal that is `true` while an attempt is
133
+ > open and its popup has not been seen closed. Under COOP it turns `false` early, which re-enables the
134
+ > buttons, while the attempt keeps listening. Awaiting the promise is fine; disabling a button until it
135
+ > settles is not. The shipped sign-in and account pages disable their buttons and show a spinner from
136
+ > this signal, with no Cancel button.
137
+
138
+ **Redirect mode.** On success the provider round trip lands on `returnUrl`, signed in. On failure it
139
+ lands on `errorUrl?sparkExternalLogin=<code>`. `errorUrl` is a call option; by default it is the page
140
+ the attempt started from: the router URL, query included (e.g. `/sign-in?returnUrl=%2Forders`), with
141
+ any earlier `sparkExternalLogin` removed so failures do not pile up. The server sanitizes it like
142
+ `returnUrl` (a local path only). Popup mode sends no `errorUrl`; its outcome comes back to the opener.
143
+ `SparkAuthService.takeExternalLoginResult()` reads the code and strips it from the address bar with
144
+ `replaceUrl`. A code it does not know is answered as `no_login_info`. The shipped provider buttons (on
145
+ the sign-in and login pages) and the account pages call it on load, so their own redirect attempts (**Continue in this tab**, the installed-app
146
+ redirect, linking) show their failures there. If you start a redirect attempt from another page, call
147
+ it on that page, or in the app shell.
148
+
149
+ **OpenID Connect providers.** The server's OIDC preset (`spark.AddOpenIdConnect(...)`, or a
150
+ `Spark:Auth:Providers:OpenIdConnect:<scheme>` section) uses a scheme name the application chooses, so its button is declared with that
151
+ scheme and a label: `withExternalLogin(oidcProvider('HR', 'Spark HR'))`. The scheme must equal the
152
+ server's exactly; the icon defaults to `bi bi-person-badge` and is the optional third argument.
153
+
154
+ **Errors.** The pages show a failure as `auth.externalLoginError.<code>`, translated. The codes are
155
+ in `SparkExternalLoginError` and include `remote_failure` (the provider reported an error) and
156
+ `invalid_nonce`. `popup_closed` is "not now" and shows nothing. `link_confirmation_sent` is shown as
157
+ a notice, not an error.
158
+
159
+ **Known limitation: Firefox on Android with the web app installed.** Firefox's installed web app
160
+ shares no BroadcastChannel or storage with the Firefox browser tab, and probably no cookies either.
161
+ In a browser tab where the app is installed, `window.open` into the app's scope returns no window and
162
+ no hand-off is possible, so the user gets `popup_blocked`. **Continue in this tab** (redirect mode)
163
+ is the way through there. Inside the installed app, `'auto'` already uses redirect.
164
+
165
+ ## Provider buttons: `<spark-external-login-buttons>`
166
+
167
+ The sign-in landing page and the password login page both show the same provider buttons, from
168
+ `@mintplayer/ng-spark/auth/external-login`. On the login page they sit above the password form,
169
+ separated by "or"; with no provider reported, neither the buttons nor the divider render, so an app
170
+ without external login sees the login page unchanged.
171
+
172
+ The component:
173
+ - lists the providers the server reports (`GET /spark/auth/capabilities`), decorated and ordered by
174
+ the `withExternalLogin(...)` declarations. A declared provider the server does not report is dropped;
175
+ - signs in with `loginWithProvider`, then navigates to the `returnUrl` input, or else to a local
176
+ `?returnUrl=`, or else to `defaultRedirectUrl`;
177
+ - disables its buttons and shows a spinner from `externalLoginPending`;
178
+ - shows the failure, including **Continue in this tab** for `popup_blocked`, and reads `?sparkExternalLogin`
179
+ on load;
180
+ - renders nothing while loading, when the capabilities fail to load, or when there are no providers.
181
+ `hasProviders()` tells a host which, for example to draw a divider.
182
+
183
+ Host it on a page of your own the same way:
184
+
185
+ ```html
186
+ <spark-external-login-buttons [returnUrl]="'/orders'" [providerTemplate]="myButton" />
187
+ ```
188
+
189
+ `providerTemplate` replaces each default button and receives a `SparkProviderButtonContext`
190
+ (`$implicit` provider, `signIn()`, `pending`). `<spark-sign-in>` passes its own `providerTemplate` and
191
+ `returnUrl` inputs through. The service joins concurrent `capabilities()` calls, so a page that also
192
+ reads them (as the login and sign-in pages do) makes one request. `takeExternalLoginResult()` answers
193
+ each failure once, so two components on one page cannot both show it.
194
+
195
+ ## Entry points
196
+
197
+ | Entry point | What it provides |
198
+ |---|---|
199
+ | `@mintplayer/ng-spark/auth` | `provideSparkAuth(config?)`, `withSparkAuth()` |
200
+ | `/routes` | `sparkAuthRoutes(...)`, `withLocalLogin`, `withRegistration`, `withExternalLogin`, `withPasskeys`, `withAccount`; provider presets `githubProvider`, `googleProvider`, `microsoftProvider`, `facebookProvider`, `twitterProvider`, `linkedInProvider`, `oidcProvider(scheme, displayName, iconClass?)`, `externalProvider(scheme)` |
201
+ | `/guards` | `sparkAuthGuard` — lets a signed-in user through, otherwise redirects to sign-in with `returnUrl`; waits for the session check on a hard reload. `sparkAuthenticatedGuard` is the same guard |
202
+ | `/interceptors` | `sparkAuthInterceptor` (included by `withSparkAuth()`) |
203
+ | `/core` | `SparkAuthService` (current user signal, `checkAuth()`, sign-in/out, `loginWithProvider`/`linkProvider`, `externalLoginPending`, `takeExternalLoginResult()`), `SparkAuthTranslationService` |
204
+ | `/models` | `SPARK_AUTH_CONFIG`, `SPARK_AUTH_ROUTE_PATHS`, `SPARK_EXTERNAL_PROVIDERS`, `SPARK_ACCOUNT_PROFILE_FIELDS`, `provideSparkAccountProfileFields(...)` and the wire types |
205
+ | `/login`, `/two-factor`, `/forgot-password`, `/reset-password`, `/register`, `/sign-in` | The individual pages the route features mount |
206
+ | `/external-login` | `<spark-external-login-buttons>` (`SparkExternalLoginButtonsComponent`), `SparkProviderButtonContext`, `SparkExternalProviderView` |
207
+ | `/account`, `/confirm-email` | The account pages above |
208
+ | `/auth-bar` | `<spark-auth-bar>` — sign-in / user menu for a top bar |
209
+ | `/pipes` | `TranslateKeyPipe` |
210
+
211
+ ## More
212
+
213
+ - [MintPlayer.Spark.Authorization README](https://github.com/MintPlayer/MintPlayer.Spark/blob/master/libs/authorization/MintPlayer.Spark.Authorization/README.md) — the server side, routes and options
214
+ - [Passkeys guide](https://github.com/MintPlayer/MintPlayer.Spark/blob/master/docs/guide-passkeys.md)
215
+ - [Authentication schemes guide](https://github.com/MintPlayer/MintPlayer.Spark/blob/master/docs/guide-authentication-schemes.md)
216
+ - [Release notes 11.0.0-preview.91 / ng-spark-auth 22.14.0](https://github.com/MintPlayer/MintPlayer.Spark/blob/master/docs/release-notes-preview-91.md)