Phuze

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

MethodDescription
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.

KeyTypeDescription
publishableKeystring (required)Your publishable_key from registration
apiBasestringOverride the API base URL. Only needed for testing.
redirectUrlstringWhere to send the user after they click the sign-in link. Must be an authorized domain. Defaults to the current page URL.
localestringForce the locale for the email + verify page (e.g. 'en', 'zh-TW'). Defaults to browser detection.
localesstring[]Restrict which locales are allowed; auto-detection picks the best match from this list.
textobjectOverride widget labels — see Text options.
themeobjectOverride colors / radius — see Theme options.
bindobjectDOM 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.session

Widget Customization

Pass any of the following to Phuze.init(). All fields are optional.

Behaviour options

KeyDefaultDescription
redirectUrlCurrent page URLWhere to send the user after they click the sign-in link. Must be on an authorized domain.
localeAuto-detectedOverride the locale for the sign-in email and verify page (e.g. 'en', 'zh-TW').
localesAll supportedRestrict which locales are allowed. Browser auto-detection picks the best match. Example: ['en', 'zh-TW'].

Text options

KeyDefaultControls
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

KeyDefaultControls
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