@gaballahtech/seba 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +4 -2
- package/CHANGELOG.md +130 -0
- package/MIGRATION.md +60 -0
- package/README.md +174 -8
- package/SECURITY.md +1 -1
- package/assets/seba-icon-favicon.png +0 -0
- package/assets/seba-icon-solid.png +0 -0
- package/assets/seba-logo-inverse.png +0 -0
- package/assets/seba-logo-monochrome.png +0 -0
- package/assets/seba-logo-primary.png +0 -0
- package/dist/assets/icons.d.ts +32 -0
- package/dist/assets/icons.d.ts.map +1 -0
- package/dist/assets/icons.js +30 -0
- package/dist/assets/icons.js.map +1 -0
- package/dist/assets/png-data.d.ts +8 -0
- package/dist/assets/png-data.d.ts.map +1 -0
- package/dist/assets/png-data.js +13 -0
- package/dist/assets/png-data.js.map +1 -0
- package/dist/browser/platform.d.ts +6 -0
- package/dist/browser/platform.d.ts.map +1 -1
- package/dist/browser/platform.js +17 -0
- package/dist/browser/platform.js.map +1 -1
- package/dist/browser/storage.d.ts +3 -2
- package/dist/browser/storage.d.ts.map +1 -1
- package/dist/browser/storage.js +26 -14
- package/dist/browser/storage.js.map +1 -1
- package/dist/cli.d.ts +61 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +223 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/client.d.ts +18 -8
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +130 -6
- package/dist/core/client.js.map +1 -1
- package/dist/core/errors.d.ts +10 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +7 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/events.d.ts +27 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +45 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +6 -1
- package/dist/core/http.js.map +1 -1
- package/dist/core/oidc.d.ts +17 -0
- package/dist/core/oidc.d.ts.map +1 -1
- package/dist/core/oidc.js +61 -27
- package/dist/core/oidc.js.map +1 -1
- package/dist/core/session.d.ts +10 -3
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +131 -20
- package/dist/core/session.js.map +1 -1
- package/dist/element.d.ts +43 -0
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +416 -0
- package/dist/element.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/pod.d.ts +50 -0
- package/dist/pod.d.ts.map +1 -0
- package/dist/pod.js +264 -0
- package/dist/pod.js.map +1 -0
- package/dist/react/index.d.ts +52 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +87 -0
- package/dist/react/index.js.map +1 -0
- package/dist/seba.d.ts +40 -1
- package/dist/seba.d.ts.map +1 -1
- package/dist/seba.js +90 -2
- package/dist/seba.js.map +1 -1
- package/dist/types.d.ts +110 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +27 -1
package/ARCHITECTURE.md
CHANGED
|
@@ -168,8 +168,10 @@ login(options)
|
|
|
168
168
|
|
|
169
169
|
The persisted record (`PersistedSession`) is the only durable state:
|
|
170
170
|
`version`, `webId`, `provider`, `clientId`, `refreshToken`, `key`
|
|
171
|
-
(`CryptoKey` + public JWK, or exported JWK), optional `scope`, `createdAt
|
|
172
|
-
**
|
|
171
|
+
(`CryptoKey` + public JWK, or exported JWK), optional `scope`, `createdAt`, plus
|
|
172
|
+
`accessToken`/`accessTokenExpiresAt` **only** for providers that issue no refresh token
|
|
173
|
+
(soft-persist, so a reload can resume until the token expires). Refresh-token sessions
|
|
174
|
+
never persist an access token.
|
|
173
175
|
|
|
174
176
|
Refresh is serialized through a single in-flight promise in `core/session.ts`, so ten
|
|
175
177
|
concurrent `fetch()` calls that all see an expired token perform one refresh, not ten
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,135 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0] — 2026-11-06
|
|
4
|
+
|
|
5
|
+
Additive release: a drop-in sign-in component, session events, Pod helpers and React
|
|
6
|
+
bindings. No behaviour change to login, refresh, storage or validation.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`<seba-login>`** — a zero-dependency custom element with shadow DOM. It starts the login,
|
|
11
|
+
completes the callback on the redirect route, exposes `login()`/`logout()`/`session`, and
|
|
12
|
+
switches between "Sign in" and "Sign out". Attributes: `client-id`, `redirect-uri`,
|
|
13
|
+
`issuer`, `web-id`, `config` (JSON `SebaConfig` overrides), `label`, `theme`
|
|
14
|
+
(`auto`/`light`/`dark`), `logo`, `account`. `redirect-uri` is optional (defaults to the
|
|
15
|
+
current page URL), `issuer-url` is accepted as a SolidOS-style alias, and `providers`
|
|
16
|
+
renders an issuer picker. The element also emits `login-success` alongside `seba:login`.
|
|
17
|
+
Register it with `defineSebaLogin()`; importing the package stays side-effect free.
|
|
18
|
+
- **Session events** — `onSessionChange`, `offSessionChange`, `onSessionExpired` and
|
|
19
|
+
`getSession()` on the facade, plus `SessionChangeEvent`/`SessionChangeReason` types.
|
|
20
|
+
Reasons: `login`, `restore`, `logout`, `expired`; `expired` fires at most once per session.
|
|
21
|
+
Listener exceptions are swallowed so application callbacks cannot break session handling.
|
|
22
|
+
- **`Seba.pod`** — `parseSubjectTriples`, `objectify` and `toTurtle`, also exported from the
|
|
23
|
+
package root zero-dependency helpers for the Turtle subset used by Solid pods.
|
|
24
|
+
- **`seba client-id` CLI** — a zero-dependency generator for the Solid-OIDC Client ID
|
|
25
|
+
Document. It validates HTTPS/loopback URLs, emits `scope`, `grant_types` and
|
|
26
|
+
`response_types`, supports repeated `--redirect-uri`, and can write the file with `--out`
|
|
27
|
+
(or print it to stdout for build scripts).
|
|
28
|
+
- **React bindings** — `@gaballahtech/seba/react` exports `SebaLoginButton` and `useSession`.
|
|
29
|
+
`react` is an optional peer dependency; the core entry point never imports it. The button
|
|
30
|
+
accepts `config` and `providers` props, and `redirectUri` is optional.
|
|
31
|
+
- **PNG brand assets** — five 512×512 PNGs in `assets/` (primary, inverse, monochrome, solid,
|
|
32
|
+
favicon) served through the `./assets/*` export, plus 48×48 PNG data URIs embedded for the
|
|
33
|
+
component. No SVG is used or shipped. The source PNGs were re-encoded losslessly (pixel
|
|
34
|
+
comparison unchanged) and now total ~24 kB instead of ~64 kB.
|
|
35
|
+
|
|
36
|
+
### Notes
|
|
37
|
+
|
|
38
|
+
- This release has **not yet been tested against a provider** in its component/React form.
|
|
39
|
+
The underlying login, refresh and DPoP paths are unchanged from 0.4.0.
|
|
40
|
+
|
|
41
|
+
## [0.4.0] — 2026-11-05
|
|
42
|
+
|
|
43
|
+
Completeness release driven by the first public full-cycle validation
|
|
44
|
+
(solidcommunity.net login → fetch → Pod write; see the validating app's TEST_REPORT.md).
|
|
45
|
+
|
|
46
|
+
### Fixed (2026-11-06, same release, second validation round)
|
|
47
|
+
|
|
48
|
+
- **All resource fetches and token requests now use `cache: "no-cache"`.** Community
|
|
49
|
+
Solid Server emits no `Cache-Control` on resource GETs, so browsers apply heuristic
|
|
50
|
+
freshness — a burst of rapid writes reads a heuristically stale pre-PUT ETag and every
|
|
51
|
+
conditional `PUT` with `If-Match` fails with `412` (reproduced and diagnosed live via
|
|
52
|
+
instrumented saves: fresh GET → correct `If-Match` → `412`, deterministically, while
|
|
53
|
+
the server never saw a race). `seba.fetch` no longer trusts local caches.
|
|
54
|
+
- **Sessions are persisted even when the provider issues no refresh token** (soft-persist).
|
|
55
|
+
The record then contains the access token + DPoP key + expiry, and
|
|
56
|
+
`restoreSession()` resumes the session until the access token expires. Only refresh-token
|
|
57
|
+
sessions never store the access token (unchanged principle: refresh token or nothing).
|
|
58
|
+
Expiry on restore clears the record with an explicit `SebaSessionExpiredError`
|
|
59
|
+
("…the provider issued no refresh token…"). This keeps reload working against providers
|
|
60
|
+
like solidcommunity.net, which advertise `offline_access` yet issue no refresh token
|
|
61
|
+
(verified from a real token exchange).
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- **Per-issuer compliance tolerances** (`tolerances?: Record<string, ComplianceTolerances>`).
|
|
66
|
+
Relax `allowNonCompliantAudience` / the new `allowNonCompliantCnf` for one provider
|
|
67
|
+
only while every other provider stays fully strict. Entries match the issuer exactly
|
|
68
|
+
or modulo a trailing slash, and override the global boolean flags. Applications that
|
|
69
|
+
talk to more than one provider should switch from the global flags to this.
|
|
70
|
+
- **`allowNonCompliantCnf`** (`SebaConfig` and `OIDCConfiguration`): escape hatch (same
|
|
71
|
+
warn-once pattern) for providers whose ID tokens omit `cnf.jkt` while still issuing
|
|
72
|
+
DPoP-bound access tokens (solidcommunity.net does exactly this). DPoP proofs remain
|
|
73
|
+
mandatory on every request; a present `cnf.jkt` is still verified against the session
|
|
74
|
+
key; signature, `iss`, `exp`/`iat`, `nonce`, `azp` and `client_id` audience checks stay on.
|
|
75
|
+
- **`error.stage`** — stable step identifier on every `SebaError` (`token-exchange`,
|
|
76
|
+
`id-token-signature`, `id-token-audience`, `id-token-cnf-binding`,
|
|
77
|
+
`authorization-response-*`, `network`, …) so applications/support can pinpoint where a
|
|
78
|
+
login failed without parsing messages.
|
|
79
|
+
- **`maxDpopNonceRetries`** (default 1, previously hard-coded) for both the token-endpoint
|
|
80
|
+
and the resource-fetch `use_dpop_nonce` challenge loops; providers rotating nonces on
|
|
81
|
+
every request benefit from 2–3.
|
|
82
|
+
- **`account`** config option: per-account storage namespace (`seba:<account>` IndexedDB
|
|
83
|
+
database plus a namespaced sessionStorage transaction key) enabling more than one
|
|
84
|
+
concurrent session in the same browser profile.
|
|
85
|
+
- **`Seba.getSessionInfo()`** returns a token-free `{ webId, provider, accessTokenExpiresAt }`
|
|
86
|
+
snapshot so applications can warn before the access token expires.
|
|
87
|
+
- **`Seba.completeLogin()`** completes a pending callback without falling back to starting
|
|
88
|
+
a login — pairs with `Seba.isCallbackResponse(url?)` for the explicit
|
|
89
|
+
"login() on click, completeLogin() on the callback route" pattern that avoids the
|
|
90
|
+
never-settling redirect promise. `login()` itself still handles both roles.
|
|
91
|
+
- **MIGRATION.md** with a `@inrupt/solid-client-authn-browser` mapping.
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
|
|
95
|
+
- **`allowNonCompliantAudience` was dead code.** Declared on `SebaConfig` and read from the
|
|
96
|
+
provider configuration during token validation, but never copied between the two: it is
|
|
97
|
+
now carried through `resolveConfig` and stamped onto every discovered configuration at all
|
|
98
|
+
three paths (login start, login completion, `SessionManager.discover`) via
|
|
99
|
+
`applyComplianceTolerances`.
|
|
100
|
+
- **`SebaNetworkError` message** now names CORS as the first browser-side suspect, instead of
|
|
101
|
+
only mentioning Node.js proxies.
|
|
102
|
+
- **logout without a provider revocation endpoint** now logs that the refresh token remains
|
|
103
|
+
valid server-side until expiry, instead of silently returning.
|
|
104
|
+
|
|
105
|
+
## [0.3.1] — 2026-11-05
|
|
106
|
+
|
|
107
|
+
Fixes found during the first real-world login attempt against `solidcommunity.net`
|
|
108
|
+
(manual browser flow, tunnel-published Client ID Document;see the app's TEST_REPORT.md).
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
|
|
112
|
+
- **`allowNonCompliantAudience` was dead code.** The flag was declared on `SebaConfig`
|
|
113
|
+
(types.d.ts:75) and documented as the opt-in for providers such as `solidcommunity.net`,
|
|
114
|
+
and `checkIdTokenAudience` read it from the provider configuration object — but nothing
|
|
115
|
+
ever copied it from the user's `Seba` config into that object. `resolveConfig` dropped
|
|
116
|
+
the unknown key, and `discoverProvider` builds the configuration purely from the
|
|
117
|
+
discovery document, so setting the flag had zero effect and the 0.2.1 hard-throw still
|
|
118
|
+
fired. It is now carried through `resolveConfig` (`ResolvedConfig`) and stamped onto
|
|
119
|
+
every discovered provider configuration at all three paths — login start (`#beginLogin`),
|
|
120
|
+
login completion (`#completeLogin`) and the `SessionManager` `discover` callback that
|
|
121
|
+
backs restore/refresh — via a new `applyComplianceTolerances` helper.
|
|
122
|
+
|
|
123
|
+
### Added
|
|
124
|
+
|
|
125
|
+
- **`allowNonCompliantCnf`** (`SebaConfig` and `OIDCConfiguration`), the same escape-hatch
|
|
126
|
+
pattern as `allowNonCompliantAudience`, for providers whose ID tokens omit `cnf.jkt`
|
|
127
|
+
while still issuing DPoP-bound access tokens (solidcommunity.net does exactly this:
|
|
128
|
+
its ID token has no `cnf` claim). DPoP proofs remain mandatory on every request, an
|
|
129
|
+
ID token that DOES carry `cnf.jkt` is still checked against the session key, and
|
|
130
|
+
signature, `iss`, `exp`/`iat`, `nonce`, `azp` and the `client_id` audience checks all
|
|
131
|
+
stay enforced. Warns once via `console.warn` when it suppresses the requirement.
|
|
132
|
+
|
|
3
133
|
## [0.2.1] — 2026-11-05
|
|
4
134
|
|
|
5
135
|
Documentation-only patch. No runtime behaviour changed; the only functional difference is
|
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Migrating to SEBA from `@inrupt/solid-client-authn-browser`
|
|
2
|
+
|
|
3
|
+
This guide maps the common operations of `@inrupt/solid-client-authn-browser` (v3) to
|
|
4
|
+
`@gaballahtech/seba`. It is written from a real migration (a Google-Tasks-style todo app
|
|
5
|
+
validated against solidcommunity.net).
|
|
6
|
+
|
|
7
|
+
| Inrupt | SEBA | Notes |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `handleIncomingRedirect()` on page load | `await seba.login()` **or** the explicit pair `seba.isCallbackResponse() && await seba.completeLogin()` | Both work. `login()` on a callback URL completes it; elsewhere it *navigates away* (document never unloads the promise). Use the explicit pair when that pattern is confusing. |
|
|
10
|
+
| `login({ oidcIssuer, redirectUrl, clientName })` | `seba.login({ issuer })` (redirect URL + client id come from one `new Seba({...})`) | |
|
|
11
|
+
| `fetchDefaultSession()` / `fetch(session)` | `seba.fetch(url, init)` | Always DPoP-signed with a fresh proof; cross-origin redirects are returned, never followed with credentials. |
|
|
12
|
+
| `getDefaultSession().info.webId` | `(await seba.getSessionInfo())?.webId` | SessionInfo is token-free and also carries `accessTokenExpiresAt`. |
|
|
13
|
+
| `getDefaultSession().info.isLoggedIn` | `(await seba.getSessionInfo()) !== null` | |
|
|
14
|
+
| `logout()` | `await seba.logout()` | SEBA also revokes the refresh token when the provider advertises revocation. |
|
|
15
|
+
| dynamic client registration | **none by design** — publish a Client ID Document; its URL is the `client_id` | See the README section "Publish a Client ID Document". Host a JSON-LD file at a stable HTTPS URL; no registration call is ever made. |
|
|
16
|
+
| `session.events.sessionRestore` / expiry events | poll `getSessionInfo()` → `accessTokenExpiresAt` | SEBA refreshes automatically; tell users *before* the access token lapses. |
|
|
17
|
+
|
|
18
|
+
## Conceptual differences worth knowing
|
|
19
|
+
|
|
20
|
+
1. **Tokens are not exposed.** Inrupt hands you the access token; SEBA never does. Access
|
|
21
|
+
tokens live in memory only, and the refresh token + DPoP key are the only persisted
|
|
22
|
+
secrets. If you think you need the raw token, re-check the design: `seba.fetch` covers it.
|
|
23
|
+
2. **A brand-new DPoP proof per attempt.** No proof cache, no sharing; replays are structurally
|
|
24
|
+
impossible. Cross-origin redirects are returned to you instead of followed with credentials.
|
|
25
|
+
3. **Provider conformance gaps are opt-in per provider.** Real-world providers (solidcommunity.net,
|
|
26
|
+
any recent CSS) violate Solid-OIDC §8.1 (`aud` without `"solid"`, missing `cnf.jkt`). Pass
|
|
27
|
+
`tolerances` for exactly those issuers — everything else stays fully strict:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const seba = new Seba({
|
|
31
|
+
clientId: location.origin + "/client-id.jsonld",
|
|
32
|
+
redirectUri: location.origin + "/callback",
|
|
33
|
+
tolerances: {
|
|
34
|
+
"https://solidcommunity.net/": { allowNonCompliantAudience: true, allowNonCompliantCnf: true },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
4. **Errors carry `code` and `stage`.** Branch on `error.code`
|
|
40
|
+
(`SEBA_ERR_SESSION_EXPIRED` → re-login; `SEBA_ERR_NETWORK` → transport/CORS) and
|
|
41
|
+
`error.stage` (`token-exchange`, `id-token-*`, `authorization-response-*`) for triage.
|
|
42
|
+
|
|
43
|
+
## Minimal app skeleton
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { Seba } from "@gaballahtech/seba";
|
|
47
|
+
|
|
48
|
+
const seba = new Seba({
|
|
49
|
+
clientId: window.location.origin + "/client-id.jsonld",
|
|
50
|
+
redirectUri: window.location.origin + "/callback",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// On mount:
|
|
54
|
+
const info = await seba.getSessionInfo();
|
|
55
|
+
if (seba.isCallbackResponse()) {
|
|
56
|
+
await seba.completeLogin();
|
|
57
|
+
} else if (info === null) {
|
|
58
|
+
// render a login button; call `seba.login()` inside onClick (it navigates away)
|
|
59
|
+
}
|
|
60
|
+
```
|
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ the same in both.
|
|
|
18
18
|
## Requirements
|
|
19
19
|
|
|
20
20
|
- Node.js 20 or newer, or any browser with Web Crypto (ES256), `fetch` and IndexedDB.
|
|
21
|
+
Chrome/Edge and Firefox are the tested browsers; Safari 17 or newer is expected to work
|
|
22
|
+
(older Safari versions fall back to an extractable-JWK DPoP key — see `SECURITY.md`).
|
|
21
23
|
- A publicly dereferenceable **Solid Client ID Document** served over HTTPS.
|
|
22
24
|
|
|
23
25
|
## Install
|
|
@@ -31,6 +33,23 @@ npm install @gaballahtech/seba
|
|
|
31
33
|
Host a JSON-LD document and use its URL as `clientId`. The `client_id` member must equal the
|
|
32
34
|
document URL, and every callback you use must be listed verbatim.
|
|
33
35
|
|
|
36
|
+
The package ships a zero-dependency generator, so the document does not have to be written by
|
|
37
|
+
hand:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx @gaballahtech/seba client-id \
|
|
41
|
+
--client-id https://app.example/client-id.jsonld \
|
|
42
|
+
--redirect-uri https://app.example/auth/callback \
|
|
43
|
+
--client-name "My Solid App" \
|
|
44
|
+
--out public/client-id.jsonld
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It validates the URLs (HTTPS, or loopback HTTP for local development), writes spec-shaped
|
|
48
|
+
metadata (`scope`, `grant_types`, `response_types`), and reminds you to serve the file as
|
|
49
|
+
`application/ld+json` at the `client_id` URL. Repeat `--redirect-uri` for additional
|
|
50
|
+
callbacks; run `npx @gaballahtech/seba client-id --help` for all options. Printing to stdout
|
|
51
|
+
(without `--out`) keeps it usable in CI or a build script.
|
|
52
|
+
|
|
34
53
|
```json
|
|
35
54
|
{
|
|
36
55
|
"@context": ["https://www.w3.org/ns/solid/oidc-context.jsonld"],
|
|
@@ -80,9 +99,76 @@ const session = (await seba.restoreSession()) ?? (await seba.login());
|
|
|
80
99
|
`login()` with no arguments opens a small built-in dialog asking for a WebID or an identity
|
|
81
100
|
provider URL. Pass `{ webId }` or `{ issuer }` to skip it.
|
|
82
101
|
|
|
83
|
-
###
|
|
102
|
+
### Sign-in button
|
|
103
|
+
|
|
104
|
+
`<seba-login>` is a dependency-free custom element. Importing the package has no side
|
|
105
|
+
effects; register the element once with `defineSebaLogin()`.
|
|
106
|
+
|
|
107
|
+
```html
|
|
108
|
+
<script type="module">
|
|
109
|
+
import { defineSebaLogin } from "@gaballahtech/seba";
|
|
110
|
+
|
|
111
|
+
defineSebaLogin();
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<seba-login
|
|
115
|
+
client-id="https://app.example/client-id.jsonld"
|
|
116
|
+
redirect-uri="https://app.example/auth/callback"
|
|
117
|
+
issuer="https://idp.example"
|
|
118
|
+
theme="auto"
|
|
119
|
+
label="Sign in with Solid"
|
|
120
|
+
></seba-login>
|
|
121
|
+
|
|
122
|
+
<script type="module">
|
|
123
|
+
const button = document.querySelector("seba-login");
|
|
124
|
+
button.addEventListener("seba:login", (event) => {
|
|
125
|
+
console.log("signed in as", event.detail.session.webId);
|
|
126
|
+
});
|
|
127
|
+
button.addEventListener("seba:error", (event) => {
|
|
128
|
+
console.error(event.detail.error);
|
|
129
|
+
});
|
|
130
|
+
</script>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The element starts the login on click, completes the callback when it is loaded on the
|
|
134
|
+
redirect route, restores the stored session on a normal page load, and switches to "Sign
|
|
135
|
+
out" once a session exists. Attributes: `client-id` is the only required one; `redirect-uri`
|
|
136
|
+
defaults to the current page URL (fragment removed). Also available: `issuer` (or the
|
|
137
|
+
SolidOS-style alias `issuer-url`), `providers` (space/comma separated issuer URLs rendered as
|
|
138
|
+
a picker when there are two or more), `web-id`, `config` (JSON `SebaConfig` overrides such as
|
|
139
|
+
per-provider `tolerances`), `label`, `theme` (`auto` | `light` | `dark`), `logo`, and
|
|
140
|
+
`account`. The element also emits `login-success` as an alias of `seba:login` for apps
|
|
141
|
+
written against `solid-ui`.
|
|
142
|
+
|
|
143
|
+
React (`react` is an optional peer dependency):
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { SebaLoginButton, useSession } from "@gaballahtech/seba/react";
|
|
147
|
+
import type { Seba } from "@gaballahtech/seba";
|
|
148
|
+
|
|
149
|
+
export function App({ seba }: { seba: Seba }) {
|
|
150
|
+
const session = useSession(seba); // null while signed out
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<>
|
|
154
|
+
<SebaLoginButton
|
|
155
|
+
clientId="https://app.example/client-id.jsonld"
|
|
156
|
+
redirectUri="https://app.example/auth/callback"
|
|
157
|
+
issuer="https://idp.example"
|
|
158
|
+
onLogin={(session) => console.log(session.webId)}
|
|
159
|
+
/>
|
|
160
|
+
<p>{session ? session.webId : "Signed out"}</p>
|
|
161
|
+
</>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The component registers `<seba-login>` on mount. `useSession` follows the session of an
|
|
167
|
+
existing `Seba` instance (the one you use for `fetch`), while the button owns its own.
|
|
168
|
+
`SebaLoginButton` also accepts `providers` (issuer URLs), `config` (JSON-serialized
|
|
169
|
+
`SebaConfig` overrides) and an optional `redirectUri`.
|
|
84
170
|
|
|
85
|
-
See [`examples/react/App.tsx`](examples/react/App.tsx).
|
|
171
|
+
See also [`examples/react/App.tsx`](examples/react/App.tsx).
|
|
86
172
|
|
|
87
173
|
## Node.js
|
|
88
174
|
|
|
@@ -169,6 +255,10 @@ logout(): Promise<void>
|
|
|
169
255
|
Clears memory and storage first, then revokes the refresh token if the provider advertises a
|
|
170
256
|
revocation endpoint. Revocation failures never block logout.
|
|
171
257
|
|
|
258
|
+
> ⚠️ When the provider advertises no `revocation_endpoint` (e.g. solidweb.org), logout only
|
|
259
|
+
> clears the local state — the refresh token remains valid on the server until it expires.
|
|
260
|
+
> Revoke it out-of-band or let it lapse.
|
|
261
|
+
|
|
172
262
|
### `Session`
|
|
173
263
|
|
|
174
264
|
```ts
|
|
@@ -181,6 +271,76 @@ interface Session {
|
|
|
181
271
|
|
|
182
272
|
No tokens, no keys, no expiry bookkeeping.
|
|
183
273
|
|
|
274
|
+
### `onSessionChange(listener)` / `onSessionExpired(listener)`
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
onSessionChange(listener: (event: SessionChangeEvent) => void): () => void
|
|
278
|
+
onSessionExpired(listener: (event: SessionChangeEvent) => void): () => void
|
|
279
|
+
offSessionChange(listener: (event: SessionChangeEvent) => void): void
|
|
280
|
+
getSession(): Promise<Session | null>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Subscribe before calling `login()`/`restoreSession()` so no transition is missed. Both
|
|
284
|
+
subscribe methods return an unsubscribe function. Listener exceptions are swallowed and
|
|
285
|
+
never interrupt the session flow.
|
|
286
|
+
|
|
287
|
+
| Surface | Event | `detail` | Emitted when |
|
|
288
|
+
|---|---|---|---|
|
|
289
|
+
| Facade | `reason: "login"` | `{ reason, session }` | An interactive login finished and the session was stored. |
|
|
290
|
+
| Facade | `reason: "restore"` | `{ reason, session }` | `restoreSession()` resumed a stored session. |
|
|
291
|
+
| Facade | `reason: "logout"` | `{ reason, session: null }` | `logout()` cleared the local session. |
|
|
292
|
+
| Facade | `reason: "expired"` | `{ reason, session: null }` | The session cannot continue; at most once per session. |
|
|
293
|
+
| Element | `seba:login` | `{ session }` | Same as the `login` reason. |
|
|
294
|
+
| Element | `seba:restore` | `{ session }` | Same as the `restore` reason. |
|
|
295
|
+
| Element | `seba:logout` | `{ session: null }` | Same as the `logout` reason. |
|
|
296
|
+
| Element | `seba:session-expired` | `{ session: null }` | Same as the `expired` reason. |
|
|
297
|
+
| Element | `login-success` | `{ session }` | Compatibility alias of `seba:login` (SolidOS naming). |
|
|
298
|
+
| Element | `seba:error` | `{ error }` | A login, logout or configuration error in the component. |
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
const unsubscribe = seba.onSessionExpired(() => {
|
|
302
|
+
showSignInDialog();
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### `Seba.pod`
|
|
307
|
+
|
|
308
|
+
Zero-dependency helpers for the Turtle subset used by Solid pods: `parseSubjectTriples`,
|
|
309
|
+
`objectify` and `toTurtle` are also exported from the package root. They handle absolute
|
|
310
|
+
IRIs, blank nodes, string/number/boolean literals, `@lang` and `^^<datatype>` suffixes, and
|
|
311
|
+
ignore `@prefix`/`PREFIX`/`BASE` directives (prefix expansion is not performed).
|
|
312
|
+
|
|
313
|
+
## Brand & logo usage
|
|
314
|
+
|
|
315
|
+
All artwork is PNG. The five 512×512 files in [`assets/`](assets) are shipped through the
|
|
316
|
+
`./assets/*` export and are also embedded as 48×48 PNG data URIs, so the sign-in button paints
|
|
317
|
+
without a network request. No SVG is generated, fetched or required.
|
|
318
|
+
|
|
319
|
+
| Variant | File | Use |
|
|
320
|
+
|---|---|---|
|
|
321
|
+
| `primary` | `assets/seba-logo-primary.png` | Accent-coloured logo on light backgrounds. |
|
|
322
|
+
| `inverse` | `assets/seba-logo-inverse.png` | White logo on dark backgrounds. |
|
|
323
|
+
| `monochrome` | `assets/seba-logo-monochrome.png` | Single-colour contexts, print, embossing. |
|
|
324
|
+
| `solid` | `assets/seba-icon-solid.png` | Filled app icon. |
|
|
325
|
+
| `favicon` | `assets/seba-icon-favicon.png` | Browser favicon and small avatars. |
|
|
326
|
+
|
|
327
|
+
```html
|
|
328
|
+
<link rel="icon" href="/node_modules/@gaballahtech/seba/assets/seba-icon-favicon.png" />
|
|
329
|
+
<seba-login ... logo="inverse"></seba-login>
|
|
330
|
+
<seba-login ... logo="none"></seba-login>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
import { sebaLogo, sebaLogoImage } from "@gaballahtech/seba";
|
|
335
|
+
|
|
336
|
+
sebaLogo.primary.png; // "assets/seba-logo-primary.png"
|
|
337
|
+
sebaLogoImage("favicon"); // "data:image/png;base64,..."
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Keep clear space of at least half the mark's height around the logo, do not recolour or
|
|
341
|
+
distort it, and do not place the accent mark on a low-contrast accent background — use the
|
|
342
|
+
`inverse` or `monochrome` treatment instead.
|
|
343
|
+
|
|
184
344
|
## Errors
|
|
185
345
|
|
|
186
346
|
All errors extend `SebaError` and carry a stable `code`:
|
|
@@ -204,8 +364,9 @@ All errors extend `SebaError` and carry a stable `code`:
|
|
|
204
364
|
- ID token signatures are verified against the discovered JWKS; `aud` must contain the
|
|
205
365
|
client ID and `"solid"` when the provider advertises `webid`; `cnf.jkt` must match the
|
|
206
366
|
session key (and is required for `webid`).
|
|
207
|
-
- The refresh token and DPoP key are
|
|
208
|
-
a
|
|
367
|
+
- The refresh token and DPoP key are persisted. When a provider issues **no** refresh token,
|
|
368
|
+
the access token is stored too (soft-persist) so a reload can resume the session until it
|
|
369
|
+
expires; refresh-token sessions never store an access token.
|
|
209
370
|
- Non-extractable `CryptoKey` storage is used when the engine supports structured cloning;
|
|
210
371
|
otherwise an exported JWK fallback is used (documented in `SECURITY.md`).
|
|
211
372
|
- Refresh-token rotation is atomic, and concurrent refreshes are collapsed into one.
|
|
@@ -222,14 +383,19 @@ restore → logout cycle was executed against that provider. If it was not, it s
|
|
|
222
383
|
|---|---|---|---|
|
|
223
384
|
| Hand-written fake provider (`tests/integration`) | Solid-OIDC-shaped: array `aud` with `"solid"`, `azp`, `cnf.jkt` | Full cycle in CI (`npm test`) | — |
|
|
224
385
|
| Community Solid Server 7.3.0 | **Not conformant** with Solid-OIDC §8.1: ID token has a string `aud` without `"solid"`, no `cnf` | Discovery, Client ID Document, PKCE/state/nonce, consent, callback, DPoP token exchange all pass; login rejected at ID token validation | No |
|
|
225
|
-
| solidcommunity.net |
|
|
386
|
+
| solidcommunity.net (CSS-based) | **Not conformant** with Solid-OIDC §8.1: ID token `aud` lacks `"solid"`, no `cnf.jkt` (same issue family as CSS #2295). Also: advertises `offline_access` but the token response carries **no refresh token** (verified 2026-11-06; requested scope was `openid webid offline_access`) | Partial cycle with a real account (2026-11-05): login + consent + DPoP **Pod write/read** verified — the written file `seba-todos.ttl` was independently fetched and matched the app's input verbatim. Uses per-issuer `tolerances` for the two conformance gaps. Reload works inside the access-token lifetime via soft-persisted sessions | **No** — partial cycle only |
|
|
226
387
|
| solidweb.org | Discovery verified; issuer is `https://solidweb.org` (no trailing slash); no revocation endpoint advertised | Discovery only | **No** — no account was available |
|
|
227
388
|
| solidweb.me | Discovery verified; issuer is `https://solidweb.me/` | Discovery only | **No** — no account was available |
|
|
228
389
|
|
|
229
390
|
> **⚠️ Community Solid Server 7.3.0**: CSS issues `id_token.aud` as a string instead of `["solid", client_id]` and omits `cnf.jkt`, violating Solid-OIDC §8.1. Seba correctly rejects this token. Track the upstream issue at https://github.com/CommunitySolidServer/CommunitySolidServer/issues/2295. Do not weaken Seba's validation to work around this.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
391
|
+
>
|
|
392
|
+
> **solidcommunity.net** runs a CSS build from the same conformance family. Login completes only with the two documented opt-ins for that provider (`tolerances: { "https://solidcommunity.net/": { allowNonCompliantAudience: true, allowNonCompliantCnf: true } }`); everything else — signature, `client_id` audience, `iss`, PKCE, DPoP proofs on every request — is fully enforced.
|
|
393
|
+
|
|
394
|
+
Full-cycle validation against a **real public provider** (solidcommunity.net, 2026-11-05:
|
|
395
|
+
login → consent → callback → token exchange → ID-token checks → DPoP fetch → Turtle write/read on
|
|
396
|
+
the Pod) is documented in the validating application's TEST_REPORT.md. Remaining unvalidated steps on
|
|
397
|
+
public providers: `restoreSession()` across a reload and `logout()` revocation. See
|
|
398
|
+
[`VALIDATION_REPORT.md`](VALIDATION_REPORT.md) for the earlier transcripts and blockers, and
|
|
233
399
|
[`CHANGELOG.md`](CHANGELOG.md) for what changed.
|
|
234
400
|
|
|
235
401
|
SEBA does not support dynamic client registration. The Client ID Document URL is the
|
package/SECURITY.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
| Authorization code interception / mix-up | Access + refresh tokens | PKCE S256, random `state`, RFC 9207 `iss` checked against the issuer the request was built for. |
|
|
8
8
|
| ID token forgery or substitution | User identity (`WebID`) | Signature verified with the provider's JWKS; `iss`, `aud`, `azp`, `exp`, `iat`, `nonce` validated; algorithm taken from the key metadata, not the token's preference. |
|
|
9
9
|
| DPoP proof replay | Pod access | A new proof (new `jti`, new signature) for every attempt; no proof cache, no TTL, no reuse option. |
|
|
10
|
-
| Token theft from the browser profile | Refresh token, DPoP key |
|
|
10
|
+
| Token theft from the browser profile | Refresh token, DPoP key, soft-persisted access token | The refresh token and key are persisted (IndexedDB). When a provider issues no refresh token, the access token is persisted too so a reload can resume the session until it expires; refresh-token sessions never store an access token. No `localStorage`; no cookies set by the library. |
|
|
11
11
|
| XSS on the application origin | Everything the origin can reach | Out of scope to *prevent*; the library minimises the window (no tokens in DOM, no globals, no logging) and documents the residual risk. |
|
|
12
12
|
| Credential leak via redirects | Access token, DPoP proof | `redirect: "manual"`; cross-origin redirect responses are returned to the caller, never re-issued with credentials. |
|
|
13
13
|
| Duplicate side effects from retries | Application state | Automatic replay is limited to replayable requests (`GET`/`HEAD`/`OPTIONS`, or a re-readable body), at most one nonce retry and one refresh retry. |
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEBA brand assets (PNG only).
|
|
3
|
+
*
|
|
4
|
+
* The 512x512 PNGs in `assets/` are shipped with the package through the `./assets/*`
|
|
5
|
+
* export. A 48x48 PNG rendering of each treatment is inlined as a data URI so the
|
|
6
|
+
* sign-in button paints without a network request and the core keeps zero runtime
|
|
7
|
+
* dependencies.
|
|
8
|
+
*/
|
|
9
|
+
/** Supported logo treatments. */
|
|
10
|
+
export type SebaLogoVariant = "primary" | "inverse" | "monochrome" | "solid" | "favicon";
|
|
11
|
+
/** One brand asset. */
|
|
12
|
+
export interface SebaLogoAsset {
|
|
13
|
+
/** Treatment name. */
|
|
14
|
+
readonly variant: SebaLogoVariant;
|
|
15
|
+
/** Human readable description. */
|
|
16
|
+
readonly label: string;
|
|
17
|
+
/** Packaged 512x512 PNG file, relative to the package root. */
|
|
18
|
+
readonly png: string;
|
|
19
|
+
/** Inline `data:image/png;base64,...` rendering for runtime use. */
|
|
20
|
+
readonly dataUri: string;
|
|
21
|
+
/** Intrinsic width of the inlined PNG. */
|
|
22
|
+
readonly width: 48;
|
|
23
|
+
/** Intrinsic height of the inlined PNG. */
|
|
24
|
+
readonly height: 48;
|
|
25
|
+
}
|
|
26
|
+
/** Manifest of every brand asset. */
|
|
27
|
+
export declare const sebaLogo: Readonly<Record<SebaLogoVariant, SebaLogoAsset>>;
|
|
28
|
+
/** Inline PNG data URI for a logo treatment, ready for an `<img src>`. */
|
|
29
|
+
export declare function sebaLogoImage(variant?: SebaLogoVariant): string;
|
|
30
|
+
/** Whether a string names a known logo treatment. */
|
|
31
|
+
export declare function isSebaLogoVariant(value: string): value is SebaLogoVariant;
|
|
32
|
+
//# sourceMappingURL=icons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../src/assets/icons.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,iCAAiC;AACjC,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,GAAG,OAAO,GAAG,SAAS,CAAC;AAEzF,uBAAuB;AACvB,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;CACrB;AAkBD,qCAAqC;AACrC,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,aAAa,CAAC,CAAU,CAAC;AAEjF,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,OAAO,GAAE,eAA2B,GAAG,MAAM,CAE1E;AAED,qDAAqD;AACrD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,eAAe,CAEzE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEBA brand assets (PNG only).
|
|
3
|
+
*
|
|
4
|
+
* The 512x512 PNGs in `assets/` are shipped with the package through the `./assets/*`
|
|
5
|
+
* export. A 48x48 PNG rendering of each treatment is inlined as a data URI so the
|
|
6
|
+
* sign-in button paints without a network request and the core keeps zero runtime
|
|
7
|
+
* dependencies.
|
|
8
|
+
*/
|
|
9
|
+
import { SEBA_PNG_DATA } from "./png-data.js";
|
|
10
|
+
function asset(variant, label, png) {
|
|
11
|
+
return { variant, label, png, dataUri: SEBA_PNG_DATA[variant], width: 48, height: 48 };
|
|
12
|
+
}
|
|
13
|
+
const ASSETS = {
|
|
14
|
+
primary: asset("primary", "SEBA logo", "assets/seba-logo-primary.png"),
|
|
15
|
+
inverse: asset("inverse", "SEBA logo (inverse)", "assets/seba-logo-inverse.png"),
|
|
16
|
+
monochrome: asset("monochrome", "SEBA logo (monochrome)", "assets/seba-logo-monochrome.png"),
|
|
17
|
+
solid: asset("solid", "SEBA icon", "assets/seba-icon-solid.png"),
|
|
18
|
+
favicon: asset("favicon", "SEBA favicon", "assets/seba-icon-favicon.png"),
|
|
19
|
+
};
|
|
20
|
+
/** Manifest of every brand asset. */
|
|
21
|
+
export const sebaLogo = ASSETS;
|
|
22
|
+
/** Inline PNG data URI for a logo treatment, ready for an `<img src>`. */
|
|
23
|
+
export function sebaLogoImage(variant = "primary") {
|
|
24
|
+
return ASSETS[variant].dataUri;
|
|
25
|
+
}
|
|
26
|
+
/** Whether a string names a known logo treatment. */
|
|
27
|
+
export function isSebaLogoVariant(value) {
|
|
28
|
+
return Object.prototype.hasOwnProperty.call(ASSETS, value);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=icons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icons.js","sourceRoot":"","sources":["../../src/assets/icons.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAqB9C,SAAS,KAAK,CACZ,OAAwB,EACxB,KAAa,EACb,GAAW;IAEX,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AACzF,CAAC;AAED,MAAM,MAAM,GAA2C;IACrD,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,8BAA8B,CAAC;IACtE,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,qBAAqB,EAAE,8BAA8B,CAAC;IAChF,UAAU,EAAE,KAAK,CAAC,YAAY,EAAE,wBAAwB,EAAE,iCAAiC,CAAC;IAC5F,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,WAAW,EAAE,4BAA4B,CAAC;IAChE,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,cAAc,EAAE,8BAA8B,CAAC;CAC1E,CAAC;AAEF,qCAAqC;AACrC,MAAM,CAAC,MAAM,QAAQ,GAAqD,MAAM,CAAC;AAEjF,0EAA0E;AAC1E,MAAM,UAAU,aAAa,CAAC,UAA2B,SAAS;IAChE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;AACjC,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated 48x48 PNG renderings of the brand artwork, inlined as data URIs.
|
|
3
|
+
*
|
|
4
|
+
* Generated from the 512x512 files in `assets/` (lossless downscale, same PNG format)
|
|
5
|
+
*/
|
|
6
|
+
import type { SebaLogoVariant } from "./icons.js";
|
|
7
|
+
export declare const SEBA_PNG_DATA: Readonly<Record<SebaLogoVariant, string>>;
|
|
8
|
+
//# sourceMappingURL=png-data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"png-data.d.ts","sourceRoot":"","sources":["../../src/assets/png-data.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAMnE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated 48x48 PNG renderings of the brand artwork, inlined as data URIs.
|
|
3
|
+
*
|
|
4
|
+
* Generated from the 512x512 files in `assets/` (lossless downscale, same PNG format)
|
|
5
|
+
*/
|
|
6
|
+
export const SEBA_PNG_DATA = {
|
|
7
|
+
primary: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAABQVBMVEUAAAB8Tf99T/98Tv97TP98TP99Tv+AUP97S/9+T/99Tf9+Tv98T/97Tf98Tf98Tf98Tf98Tf98Tv98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf99Tv98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf99Tv9+Tv98Tf98Tf98Tf98Tf98Tf98Tf98Tf99Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tv98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf99T/98Tv98Tf/B0+kdAAAAanRSTlMAAAAAAAAAAAAAAAAAABsOZNIERrn7LZvxGXvh5oYLXMz0ozMCP7H5/cBOBieT7dhsEhVz3OqMIwhUxf73qzoBAQM4qfbHVgkDTOnddRZvavCVKLCvaaEDFxwe35ZoD02i+L0HiqVEBAECSK4URgAAAn5JREFUSMetVuda4lAQzSESNVixd8ESS+wdVLB3RMECCFmpet7/Afa7UXbDkqDs5/xL7pzM3DMzZyJJP2JwAS58210G0NYGQP62e3tHZ2dH+/cgkOHt6u4he7q7vJC/yktugLu3r5+m9ff1utFQK4gCYGBwiOTwyMgwyaHBAQCKg3sjgNGxcZI+/0RT04TfR3J8bBRAo0Pyk1PTGjkzOzcPAPNzszOkNj01CQW22egLiySXllcANDcDWFleIrm4oFfxJdxX19ZJbmxuiVNVVVXB79bmBsn1tdXKqwDY3gkEyd29/ZAZ3xMOeyQJCkL7e7tkMLCzDfzNCweHR8dk8OT0DIAqIuq6+KIK4Oz0JEgeHx2eWwAXlySvrm/KuboRicBdrvvN9RXJywsL4JaM3t3DNNF08MbiXnw0obD7uyh5awE8kI9Pog/wnBBRkEylkhBfTzxDEP70SD5UAl7M8iDhT2cAGJpmAMikfyVglvTFDiBJUgvSr1kjhzyZR87IvqbhMml0AsjIZOkrFANkoFjwMZsxWXAGSIDhI6MaqUVJn1F+6wiQkSvwjxVyHzQ7AZRWwWExWvaPFsVzq+IEUIBS0sgHtDJAC+SNZMk8sAOoIT0ST2msMC0Vj+gh1R4Q1iOxakAsoofVn0kJkuy2u7RbrkWr8i+tivSzhau7NVx1NZ9de/sd27vWAClVA2Qd0RZZklCKxUuQJLnFYUQtIuCqFAGXvQhUyIxHkKXrgh6PVWYOLACLkL1VCtmbrZBVSaUipFJxlspPwbKKsUepKcafcv9uL/fvDpur3oVS/8oy86peil/tUQCl8totVVDpZPUu9vp/Hcy8FNhtzv+y36MAAZmg32CuAAAAAElFTkSuQmCC",
|
|
8
|
+
inverse: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAABRFBMVEULCw8JCQ0KCg4ICAwlJSkYGBxra23U1NQPDxNOTlG8vL37+/v///82Njmfn6Hy8vIjIyeBgYPj4+Pn5+eLi40jIyYVFRljY2XOzs/19fWnp6k8PD8NDRFHR0u1tbb5+fn9/f3Dw8RVVVgRERVWVli0tLZHR0owMDSXl5nu7u/a2tpzc3UcHCDu7u4fHyJ5eXve3t7r6+yRkZMsLDAICA0TExdbW17IyMn+/v739/evr7BCQkUMDBAODhJBQUStra/JycpdXWAwMDOtra5UVFfq6urf3997e30gICMHBwt1dXhUVFZxcXPx8fGZmZsyMjUJCQ6zs7Rvb3KlpachISUoKCvg4OEmJimampynp6hubnEaGh76+vqmpqibm5z4+PjAwMFVVVcGBgqPj5GPj5BubnCpqapMTE/IyMipqasxMTV2dnikVltMAAACNklEQVRIx61W6T/jUBR9SxEcjGUQOkJiMPal1JIaaizpCIoWbYcu2jL+/+/zS9J12qRJf+7Hl3vuPffl3nMfIZ9klHpyZ9zn48y9P+/o7Orq7OBu3YXunl6gt6dbcAOhrK9/AKYN9PexVqUw/mVwCMDwyMgwgKHBr86l8NGxcQDixCRjkxMigPGxUXte1P9tSgKmZ2SFE8KV2ZlpQPo+52/Oi9H5hR8AFpeWLRqMLy8tAlhZmKeNvBhfXVsHsLG5FWDVGFubGwDW11b/L4Vv7wR3gb39A9XKr6oWS/Vgfw/YDe5s15USOvxpHB8dl49pOEzLoY6PjFAnh6Ga+L9OAZydX1QSa5GIViF7cX4G4PR3NYd2CehX1/SGEGZBhGhUsNwZITf0+koHLrUq4Ba4CwWM7/dm09FYPB6jRvSHeyNCIHQH3NYDHk3OzPeUSHKmyamUrDGeTPzxmSnpYwPg2eLCEy/pjJLNAa9ZRU6/5C3i7NkOwJJpiIViEHgrvotIJ1kLAOEZEdAlQNIBMVO6GQcAU95RsYLCWgGoli3qZX+9mNWoE4BqQkzOBaUyQArm5JhgYJoD1HAkGk+hzlLxaCSsfhagkdLbqyMl70W3c62ef1xtaxTctEZd82Wcmq/U3g9P+dr2ztu3t80A+ZoPkDmif92PaBMR+PhwEgESOjzxJDPehcxOKgO2UlkS4xX3Ymzm989Zcj9bK/dTdnJPvC+UNlaW96VIvK9d4n2xt/F08P44cbB/hbqdaSuT7DQAAAAASUVORK5CYII=",
|
|
9
|
+
monochrome: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAADKElEQVRYw+XYQUhVaRQH8J9pz+RBQlCRSZtgchMONDDhQxqGkFlkQjiB0MZoUREoRIuhxdDGNi2iMAZaSARFNDQjzmIKEUHetHGRrWooopAHBoEPNHma3Vm8q17nvtfT8sLA/N/mnXPP+Z97zvd95/u+y/8Nu+1OjnyrU5566pStG09eo81DCwKBBQ+1qdlI+v1uygsiv7yb9m8M+S4XvQ5pc/r0yYXSaxft+jLytBPGQ7oZtx0AB9w2E2rHnZD+PPJNDhlUEAgsGnVUavlZylGjFgUCBYMO2bRe+n2uexe+5TPnbItZbHPOs9Dinev2rZ18u/NehK5vXbG3rOVeV7wNLV84b3tl8i06ZX0UCMy5r0XVqudVMbnFfXMCgY+yOm0pT17toHveh8aPHVdXonTxUtQ57nH4Uu/dc1B16QBHTIXpvnTBjpI2PXpK6ne44GXoPeVI6QBnBQLT+jWVybHG734ru4ab9JsWCJwtbXBGIHBrVYI7NUakPV55ZU9E02jnqiLfEgicWVHF5++sxYi02WWn1YdSswYNmkOp3ml9NkesF83+m67SApmUddUD7WrRKiWlFbXaPXDVXyY/TVC5J971o+99a9CADMg4rFuHtBF3K/rHxuBGTN8edp7psHEUwqGc0R6zvVF5DOJ4ZDCsebEbpcIxGfSosvNaAhQMyMe0eQMKlZ1rKjxt0KxVpsSarnNJmzETcj6sP0CVr/zgO19riLTpKFJatOiR88SoP/0tWH+JqqwFn7Qql0Hguef6l0v0TSyPeeOyn1+iIj54440hh/0aCzDnZ8OV01vLLKrVvdwsVlCvW+3GBGjTAfLmw+IUJ22Hto0IUK9X2qw7Oo2DcZ3umJXWWyKzdQfokjGiy0nDsiBr2EldRmR0fWmARhm9jhlSwJh588ZQMOSYXplV+0UJxGdRWnVkR1jwU6QhT8hhIpTyfvGHhYhvdfwQFs2guGA6XItsmVOr+n3OhCdyEc2kqeX/Ta6F06HM0kt800/82CLpg9cSEj06rpQiwcNvEQkf34tI9AKyhESvUEtI9BJYRMLX2CISvYgvIdFPCf9N/APu8294oVkmtwAAAABJRU5ErkJggg==",
|
|
10
|
+
solid: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAABAlBMVEUAAAB8Tv98Tf97TP98TP99Tv+AUP9/Uv+AUv97S/9/U/98Tv98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf99Tv98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf9+T/98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf98Tf99T/98Tv98Tf97S/+QaP96Sv+1m/97TP9+UP/WyP/Wx/+IXf+KYP96S/+LYv/u6f/u6P/Pvv+xlf+EWP+kg//8+//7+v/d0f9/Uv95Sf/Drf/////59//RwP+ffP/h1//39f/39P+jg/+iTbl8AAAANXRSTlMAAAAAAAAAAAAAAAREBC2b8Rl74QtczAI/sfknk+0Vc9wIVMX+ATip9iGK6Q25E9LRIuoBAmJSODcAAAHNSURBVEjH1ZXZVsIwEIbpICqKuAuILIKK4i5MCWUpoG0jSygV3/9VPMWCLdLScOPxv8nJ6XzNTJZ/AoH/IQEmEvzGgwDbkcgOCODv90FYi+7u7e1G1yC4fJEQAOwfHCIiHh7sA0DIM3wdAI6OT9DSyfERAKx7Jh+LJ05xptNEPAYht1LMbJJnKXQodZZ02S8zPJ3J4i9lM+kFpZg7f57LX+ACXeRz5/OrwAZcXhWuf4LESkX8mV0Xri5h014KhG+Kt/a/kmqV2Oe3xZuwA7i7t75I0mSo1Rs1+xzx/s4BPDxa8U25JSFK7c5r2xxbctMiHh+cwJMFyG+KSpBolGoEiaq8yRbwtBjAlvLe1Xr9AWODfk/rvist9AYktcuG+shgzBjpQ9ZVpSUAEm3I2AdljI4ZG37O9soVwJ7OZtJ7uAyQSH/0MY0fj/rEMyWR1NrawKBTgBoDrV0joitQqTY6lDlEO41qxQOo/wbqHgB3StxFr7Kt3wc39n9w3FfDvHyfHJfP//XmfUC8T1QIPxdflpmA3WeELSh520wJthzGxGtk/FY5XYXDjAPfdl+et/uyu92v0FD4W9YkL3tT9NdHAWDHbLvgL3yFxv7H+gL4b+/cCTQELQAAAABJRU5ErkJggg==",
|
|
11
|
+
favicon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAzFBMVEX////9/P/c0P/o4P+vk/+DV//y7v/Aqf+LYv96S/97TP/59//Qv/+Wcf98Tf/+/f/f0/+kg/9/Uf/r5P+0mf+FWf/08f/Er/+OZf97S//7+f/UxP+adf99Tv/i2P+oif+AU/+QaP/u6P+4nv+HXP98TP96Sv+1m//49v+gfv9+UP/WyP/Wx//18v+TbP+IXf+KYP/u6f+Ubf/Pvv+xlf+EWP/8+//7+v/d0f9/Uv95Sf/Drf/RwP+ffP/h1//39f/39P+jg////v+0mP8Z39bnAAABg0lEQVRIx92VaXOCMBCG15sEXY033mdUvFABTzza//+fOhYtZVQkfGv3SyY778O8JJtdgH8boXA4JKKPRGOxaMS3PJ6QCKVESsR9yeVkCikiIsVUUn4rT2dYluAtSJZl0t76XL7wI/9GCvmch7xYKisUXUGVcqn4Ql6p1uoKPoRSr1Urz/SNZstxo7TbDktazcaDvNPtkb7zWT4YcGfXJ71ux60fjrhtntqLOp6ov/eUj4YuYDqz7dC5RigilRYd6boSbW4TZDZ1A0vbMtVWusGRM9NkHLmhrzQbUJbPAST6esO2uz3Afrdlm7V+O4iXADU2IB+sI8DROsiwMegbADmTAU4mgHkGkC/3s3oN4PbgpA9bfAdQvrNO9+zZ2nFPSwpXJbY/mvesedwzSeXKS6A9mCxMVx7MxWTQ9gDGj8DYAxC2JPzTQY7Vvriz/4sTLo1r8V0Eis9/eYs+IOEnKtwEAD4E20yARibeKq+R+xRqxgHavfhAAfGRBeJDMcDY/VvxBc2dXnlb2TJpAAAAAElFTkSuQmCC",
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=png-data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"png-data.js","sourceRoot":"","sources":["../../src/assets/png-data.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,CAAC,MAAM,aAAa,GAA8C;IACtE,OAAO,EAAE,ghDAAghD;IACzhD,OAAO,EAAE,wxCAAwxC;IACjyC,UAAU,EAAE,4pCAA4pC;IACxqC,KAAK,EAAE,4oCAA4oC;IACnpC,OAAO,EAAE,w4BAAw4B;CACl5B,CAAC"}
|
|
@@ -9,6 +9,12 @@ import type { LoginOptions, Platform } from "../types.js";
|
|
|
9
9
|
* @throws {@link SebaConfigError} when no browser `window`/`document` exists.
|
|
10
10
|
*/
|
|
11
11
|
export declare function createBrowserPlatform(): Platform;
|
|
12
|
+
/**
|
|
13
|
+
* Derive a namespaced platform for multi-account support: each account gets its own
|
|
14
|
+
* IndexedDB database and sessionStorage transaction key so parallel `Seba` instances
|
|
15
|
+
* never overwrite each other's session.
|
|
16
|
+
*/
|
|
17
|
+
export declare function namespaceBrowserPlatform(platform: Platform, account: string): Platform;
|
|
12
18
|
/** Re-exported for {@link Seba} type inference in browser-only builds. */
|
|
13
19
|
export type { LoginOptions };
|
|
14
20
|
//# sourceMappingURL=platform.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/browser/platform.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAA2B,YAAY,EAAE,QAAQ,EAAsC,MAAM,aAAa,CAAC;AAUvH;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,QAAQ,CAqDhD;AAED,0EAA0E;AAC1E,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/browser/platform.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAA2B,YAAY,EAAE,QAAQ,EAAsC,MAAM,aAAa,CAAC;AAUvH;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,QAAQ,CAqDhD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAWtF;AAED,0EAA0E;AAC1E,YAAY,EAAE,YAAY,EAAE,CAAC"}
|
package/dist/browser/platform.js
CHANGED
|
@@ -57,4 +57,21 @@ export function createBrowserPlatform() {
|
|
|
57
57
|
},
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Derive a namespaced platform for multi-account support: each account gets its own
|
|
62
|
+
* IndexedDB database and sessionStorage transaction key so parallel `Seba` instances
|
|
63
|
+
* never overwrite each other's session.
|
|
64
|
+
*/
|
|
65
|
+
export function namespaceBrowserPlatform(platform, account) {
|
|
66
|
+
return {
|
|
67
|
+
...platform,
|
|
68
|
+
pendingAuth: createSessionStoragePendingAuthStore(account),
|
|
69
|
+
createStorage(kind) {
|
|
70
|
+
if (kind === "memory") {
|
|
71
|
+
return memorySessionStorage();
|
|
72
|
+
}
|
|
73
|
+
return createIndexedDbStorage(account);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
60
77
|
//# sourceMappingURL=platform.js.map
|