Widget (phuze.js)
← Developer Docs · Core Concepts · API Reference · Agent skill: phuze-skill.md
The widget adds passwordless email login with one script tag. It is self-contained — it renders the email sign-in form when logged out and the user's email + a "Sign out" button when logged in, transitioning automatically.
Including the widget
<script src="https://phuze.edato.me/phuze.js"></script>
<script>
Phuze.init({ publishableKey: 'pk_live_...' })
Phuze.mount('#phuze-widget')
</script>
<div id="phuze-widget"></div>Widget API
| Method | Description |
|---|---|
Phuze.init(opts) | Initialize — call once with your publishableKey |
Phuze.mount(selector) | Mount the widget into a DOM element — self-contained, manages both logged-out and logged-in views |
Phuze.getSession() | Returns Promise<PhuzeSession | null> — null if no active session or any failure. Makes a network call each time. |
Phuze.logout() | Log out the current user — widget re-renders automatically. Resolves to { logged_out: true } |
Phuze.onSessionChange(fn) | Subscribe to session changes — use to update your own app UI, not to control the widget. Also fires on page load when a session is found. |
Two more (Tier 3) for framework lifecycles are covered under Subscribe API: Phuze.subscribe(fn) (returns an unsubscribe function) and Phuze.session (synchronous).
Phuze.init(opts)
Must be called before anything else. Only publishableKey is required.
| Key | Type | Description |
|---|---|---|
publishableKey | string (required) | Your publishable_key from registration |
apiBase | string | Override the API base URL. Only needed for testing. |
redirectUrl | string | Where to send the user after they click the sign-in link. Must be an authorized domain. Defaults to the current page URL. |
locale | string | Force the locale for the email + verify page (e.g. 'en', 'zh-TW'). Defaults to browser detection. |
locales | string[] | Restrict which locales are allowed; auto-detection picks the best match from this list. |
text | object | Override widget labels — see Text options. |
theme | object | Override colors / radius — see Theme options. |
bind | object | DOM binding options — see DOM Binding. |
Session type
interface PhuzeSession {
active: true
uid: string
email?: string
expires_at: string // ISO 8601
}getSession() and onSessionChange always resolve/emit null — never a plain object — when there is no active session or on error. A truthiness check is safe.
Note: PhuzeSession does not contain the raw session token. The token used for server-side verification is stored separately in localStorage.getItem('phuze_session') — see Quick Start step 4.
Self-contained widget
Phuze.mount(selector) renders the email form when logged out and the email + "Sign out" button when logged in — transitions are automatic, no extra code. Mount it once and leave the target element permanently in the DOM (hide/show with CSS if needed, never conditionally render it). If the element is absent at mount time, mount() is a no-op.
<!-- Always present in the DOM -->
<div id="phuze-widget"></div>
<script>
Phuze.init({ publishableKey: 'pk_live_...' })
Phuze.mount('#phuze-widget')
// onSessionChange is for YOUR app UI — the widget updates itself
Phuze.onSessionChange(function(session) {
document.getElementById('main-content').style.display = session ? 'block' : 'none'
})
</script>In React: keep the mount target unconditionally in the JSX and call mount() in a useEffect on script load. Do not conditionally render the element.
DOM Binding
Hook any element to the session state without writing render functions. Bindings apply at Phuze.mount() and stay in sync on every session change. Opt out per feature via bind in Phuze.init(): bind: { bodyClass: false } disables the state classes (or pass a CSS selector string to target an element other than <body>); bind: { domAttributes: false } disables data-phuze-* scanning.
Body state classes
Phuze applies exactly one of phuze-checking, phuze-active, or phuze-anonymous to <body> on every session change including page load. Auth-driven UI becomes pure CSS:
.phuze-anonymous .signup-banner { display: block; }
.phuze-active .signup-banner { display: none; }
.phuze-active .dashboard-link { display: block; }
.phuze-anonymous .dashboard-link { display: none; }
.phuze-checking .auth-gated { visibility: hidden; }Avoid the initial flash: the script loads async, so hardcode the checking state in your HTML — <body class="phuze-checking">. The widget adopts a pre-existing class (never duplicates it). If you can't edit the body tag, add a synchronous one-liner in <head> that adds the class on DOMContentLoaded.
Data attribute bindings
<!-- Show/hide by session state (original display restored on show) -->
<a data-phuze-show="active" href="/dashboard">My Dashboard</a>
<button data-phuze-show="anonymous">Sign In</button>
<!-- Bind text to a session field; original content is the logged-out fallback
(auto-captured into data-phuze-text-default; set it yourself to override) -->
<p>Hello, <span data-phuze-text="email">there</span>!</p>
<!-- Bind an attribute to a template; {uid} {email} {expires_at} only;
href/src values URL-encoded; original value restored on logout -->
<a data-phuze-attr="href:/account?user={uid}" href="/login">My account</a>All updates use textContent / setAttribute — bindings never inject HTML. Only uid, email, and expires_at interpolate; anything else is left as-is.
Subscribe API
// Returns an unsubscribe function — safe in React useEffect / Vue onMounted
const unsub = Phuze.subscribe(function(session) { /* ... */ })
unsub() // cleanup
// Synchronous session access (undefined = checking, null = logged out)
const session = Phuze.sessionWidget Customization
Pass any of the following to Phuze.init(). All fields are optional.
Behaviour options
| Key | Default | Description |
|---|---|---|
redirectUrl | Current page URL | Where to send the user after they click the sign-in link. Must be on an authorized domain. |
locale | Auto-detected | Override the locale for the sign-in email and verify page (e.g. 'en', 'zh-TW'). |
locales | All supported | Restrict which locales are allowed. Browser auto-detection picks the best match. Example: ['en', 'zh-TW']. |
Text options
| Key | Default | Controls |
|---|---|---|
text.button | 'Sign in with email' | Idle button label |
text.buttonLoading | 'Sending…' | Button label while request is in flight |
text.buttonSignOut | 'Sign out' | Sign out button label in logged-in view |
text.placeholder | 'your@email.com' | Email input placeholder |
text.sentTitle | 'Check your email for a sign-in link.' | Sent state heading |
text.sentSubtitle | 'This tab will update automatically…' | Sent state sub-message |
Theme options
| Key | Default | Controls |
|---|---|---|
theme.accentColor | '#6366f1' | Button background + input focus ring |
theme.accentHoverColor | '#4f46e5' | Button hover background |
theme.accentTextColor | '#ffffff' | Button label color |
theme.borderRadius | '6px' | Input, button, and sent-box corner radius |
theme.borderColor | '#d1d5db' | Input border color |
Example
Phuze.init({
publishableKey: 'pk_live_...',
redirectUrl: 'https://myapp.com/dashboard',
locale: 'zh-TW',
locales: ['en', 'zh-TW'],
text: {
button: 'Log in',
placeholder: 'you@company.com',
},
theme: {
accentColor: '#0f172a',
accentHoverColor: '#1e293b',
borderRadius: '4px',
},
})Styling
Every widget element uses a phuze- prefixed class you can override in your stylesheet (for anything not covered by theme):
.phuze-form { ... } /* email form wrapper */
.phuze-input { ... } /* email input */
.phuze-btn { ... } /* submit button */
.phuze-sent { ... } /* "check your email" box (.phuze-sent-sub = its sub-message) */
.phuze-user { ... } /* logged-in row wrapper */
.phuze-user-email { ... } /* email display */
.phuze-signout-btn { ... } /* "Sign out" button */Phuze · phuze.edato.me · Developer Docs · API Reference