@zoreal/oauth2-js 0.1.6 → 0.1.8

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @zoreal/oauth2-js
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@zoreal/oauth2-js)](https://www.npmjs.com/package/@zoreal/oauth2-js) [![types](https://img.shields.io/npm/types/@zoreal/oauth2-js)](https://www.npmjs.com/package/@zoreal/oauth2-js) [![CI](https://img.shields.io/github/actions/workflow/status/Bynn-Intelligence/zoreal-oauth2-js/ci.yml?branch=main&label=CI)](https://github.com/Bynn-Intelligence/zoreal-oauth2-js/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
3
+ [![npm](https://img.shields.io/npm/v/@zoreal/oauth2-js)](https://www.npmjs.com/package/@zoreal/oauth2-js) [![types](https://img.shields.io/npm/types/@zoreal/oauth2-js)](https://www.npmjs.com/package/@zoreal/oauth2-js) [![CI](https://img.shields.io/github/actions/workflow/status/Bynn-Intelligence/zoreal-oauth2-js/ci.yml?branch=main&label=CI)](https://github.com/Bynn-Intelligence/zoreal-oauth2-js/actions/workflows/ci.yml) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Bynn-Intelligence/zoreal-oauth2-js/badge)](https://scorecard.dev/viewer/?uri=github.com/Bynn-Intelligence/zoreal-oauth2-js) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
4
4
 
5
5
  Login with ZOREAL for the browser, framework-free: a ZOREAL Verified
6
6
  Proof-of-Human behind every sign-in.
@@ -89,11 +89,8 @@ const handle = startLogin({
89
89
  flow: 'auth-code',
90
90
  clientId: 'ast_your_asset_id',
91
91
  scope: 'openid email profile.name',
92
- onState: (s) => {
93
- // Render the pairing UI from this: s.qrUrl in an <img>, s.status as text,
94
- // s.cancel on your close button. Every callback carries all of it.
95
- renderPairing(s);
96
- },
92
+ // The QR, its live status, the countdown and the cancel wiring are drawn
93
+ // by this package. Nothing to render, nothing to translate.
97
94
  });
98
95
 
99
96
  const { code, code_verifier, nonce } = await handle.promise;
@@ -112,10 +109,7 @@ await fetch('/api/auth/zoreal', {
112
109
  ```ts
113
110
  import { startLogin } from '@zoreal/oauth2-js';
114
111
 
115
- const handle = startLogin({
116
- clientId: 'ast_your_asset_id',
117
- onState: (s) => renderPairing(s),
118
- });
112
+ const handle = startLogin({ clientId: 'ast_your_asset_id' });
119
113
 
120
114
  const { credential } = await handle.promise;
121
115
  // `credential` is an ID token carrying a stable per-user identifier (`sub`)
@@ -124,10 +118,71 @@ const { credential } = await handle.promise;
124
118
  // JWKS before trusting it.
125
119
  ```
126
120
 
127
- On desktop, `onState` gives you a QR to render; the user scans it with their
128
- phone and approves in the ZOREAL ID app. On a phone, `startLogin` opens the
129
- app directly through the pairing link and the promise settles when the user
130
- returns. Either way your page just awaits `handle.promise`.
121
+ On desktop this package opens the [pairing modal](#the-pairing-modal); the user
122
+ scans the QR with their phone and approves in the ZOREAL ID app. On a phone it
123
+ skips the QR and opens the app directly through the pairing link. Either way
124
+ your page just awaits `handle.promise`.
125
+
126
+ ## The pairing modal
127
+
128
+ On desktop a QR sign-in cannot complete unless something puts the pairing code
129
+ on screen, so this package does it. `startLogin` mounts a dialog, keeps it in
130
+ step with the pairing, and takes it down when the flow settles. You render
131
+ nothing.
132
+
133
+ | | |
134
+ | --- | --- |
135
+ | **Mobile** | No QR. The pairing link opens the ZOREAL ID app and the modal never appears. Force one or the other with `display: 'qr'` / `'link'`. |
136
+ | **Live status** | Copy and title follow the pairing: waiting for a scan, then waiting for approval once the code is claimed (the spent QR blurs out behind a phone glyph). |
137
+ | **Countdown** | Counts down to expiry, turning amber under 20s. Reads the clock each tick rather than decrementing, so a backgrounded tab comes back honest. |
138
+ | **Timeout** | Closes and cancels at zero. Defaults to 120s; override with `pairingTimeoutMs`. The provider's own expiry wins when it is shorter. |
139
+ | **Cancel** | The X, the Cancel button, `Escape`, clicking outside and the timeout are one behaviour: abort the poll, close, reject the promise with `AbortError`. |
140
+ | **No ZOREAL ID yet** | A footer says the same code also installs the app. Without it the panel reads as "scan this with something I do not have", and the flow dead-ends at the one moment it can still be recovered. |
141
+ | **Themes** | `theme: 'auto'` (default) follows `prefers-color-scheme`; `'light'` and `'dark'` force it. The QR well stays white in dark mode on purpose — an inverted QR fails on a good number of phone cameras. |
142
+ | **Language** | Ships its own copy in 39 locales. Pass `locale` and the modal matches the page it opened on; omit it and it follows the browser's preference list, English as the floor. Arabic, Hebrew and Urdu flip the dialog to RTL. |
143
+ | **Accessibility** | `role="dialog"`, `aria-modal`, labelled by its title, focus moved in on open, scroll locked, visible focus rings, and a `prefers-reduced-motion` fallback. |
144
+
145
+ ```ts
146
+ startLogin({
147
+ clientId: 'ast_your_asset_id',
148
+ locale: 'sv', // omit to follow the browser
149
+ theme: 'auto', // 'light' | 'dark'
150
+ pairingTimeoutMs: 120_000,
151
+ });
152
+ ```
153
+
154
+ Styling is a single `<style>` tag injected once, every selector prefixed `zrl-`
155
+ and scoped to the dialog. There is no CSS file to import and nothing to add to
156
+ your build, because a required import step is a required support ticket. The
157
+ DOM is built with `createElement`, never `innerHTML`: this renders on someone
158
+ else's sign-in page.
159
+
160
+ ### Languages
161
+
162
+ Arabic · Bengali · Bosnian · Bulgarian · Chinese (Simplified) · Chinese (Traditional) ·
163
+ Croatian · Czech · Danish · Dutch · English · Filipino/Tagalog · Finnish · French ·
164
+ German · Greek · Hebrew · Hindi · Hungarian · Indonesian · Italian · Japanese · Korean ·
165
+ Malay · Norwegian · Polish · Portuguese · Portuguese (Brazil) · Romanian · Russian ·
166
+ Serbian · Spanish · Spanish (Latin America) · Swedish · Thai · Turkish · Ukrainian ·
167
+ Urdu · Vietnamese
168
+
169
+ Resolution handles the cases that usually get missed: `zh` splits by script rather
170
+ than region, `es-MX` and the other Latin American regions resolve to Latin American
171
+ Spanish instead of peninsular, `pt-BR` stays out of European Portuguese, and the
172
+ superseded codes (`iw`, `in`) plus `nb`/`nn` and `fil` reach the right table.
173
+
174
+ ### Rendering it yourself
175
+
176
+ Framework wrappers and anyone with their own design system opt out:
177
+
178
+ ```ts
179
+ startLogin({ clientId: 'ast_your_asset_id', pairingUI: 'none' });
180
+ ```
181
+
182
+ `onState` then carries everything you need on every state: `qrUrl`, `pairUrl`,
183
+ `status`, `expiresIn` and `cancel`. Render `qrUrl` in an `<img>`; do not draw
184
+ your own. `mountPairingModal` is also exported if you want the real dialog but
185
+ driven on your own terms.
131
186
 
132
187
  ## The handle
133
188
 
@@ -478,8 +533,14 @@ identical in the browser.
478
533
 
479
534
  A wrapper owns exactly two things: calling `startLogin` on the user's
480
535
  gesture, and rendering what `onState` carries. Everything else - PKCE, state,
481
- nonce, poll cadence, cancellation - is this package's job. A minimal Vue 3
482
- composable:
536
+ nonce, poll cadence, cancellation - is this package's job.
537
+
538
+ Decide first whether you are rendering the pairing UI at all. If the built-in
539
+ modal is what you want, drop `onState` and there is nothing to build. If you
540
+ are drawing your own, pass `pairingUI: 'none'` or your users get two QRs on
541
+ screen. The example below draws its own, so it opts out.
542
+
543
+ A minimal Vue 3 composable:
483
544
 
484
545
  ```ts
485
546
  // useZorealLogin.ts
@@ -500,6 +561,8 @@ export function useZorealLogin(clientId: string) {
500
561
  active?.cancel();
501
562
  const handle = startLogin({
502
563
  clientId,
564
+ pairingUI: 'none', // this wrapper renders its own
565
+
503
566
  onState: (s) => (pairing.value = s),
504
567
  });
505
568
  active = handle;