Maru sign-in in a module
Let people sign in to your module with their Maru account, save presets to it and reach your module's Maru pages, with one account button the SDK adds for you.
A musician opens your reverb on its Maru page and clicks the round account button in the module's header. A Maru window asks once whether to connect your module to their account. After that, the button shows they are signed in, and its menu saves and loads their own presets, opens your module's page and lists your other modules. Your face never sees a password, token or email.
This page shows how to turn that on, try it locally and use the account in your own controls.
How it fits together
Sign-in is a host capability, like generation or file import. The host that
runs your face (your module's Maru page, Studio's preview) owns the account and
the sign-in window. Your face receives face.appAuth: who is signed in (as an
opaque id), and a few actions it can ask for.
| Part | Owner | What it does |
|---|---|---|
| Your module's app | Maru, one registration per module | The name people see when they connect, and what it may do (identity, presets, optionally generation) |
face.appAuth | The host | Sign in, sign out, list/save/load the person's presets for this module, open Maru pages |
ModuleFaceAccount | The SDK (maruaudio/ui/module-face-controls) | The account button and its menu, drawn in your face's own colors |
Hosts without accounts, such as an offline native plug-in, omit face.appAuth.
The button then does not appear, and the rest of your module works as before.
1. Try it locally
Start the workbench with a simulated account:
maru dev --sign-in
(Or add ?sign-in=1 to an open workbench tab.) The face gets the same
face.appAuth it gets on Maru, backed by a local test account. Sign-in takes a
moment so you can see the waiting state. Presets you save stay in this browser.
Nothing is sent to Maru.
If your face uses FaceModuleNavbar, the account button is already in its
header. There is nothing to add.
2. Turn it on for real
Publish your module:
maru publish
When your account has a handle, the module gets its own address,
https://<name>.maru.page, and a Maru app for sign-in and presets is
registered for it automatically. Check it with:
maru module app status
It reports faces: sign-in only for an app that signs in and saves presets
(ready once generation is enabled too). You can rename the app or change its
access in Creator apps.
To let the module generate with each person's credits as well, run
maru module app enable. See
Add generation to an instrument.
Spending needs the person's explicit approval each time they connect, so a
generation app never connects automatically (see below).
3. Place the button yourself (optional)
FaceModuleNavbar adds the button to its actions. With your own header, render
it where you want it:
import type { AudioModuleFaceProps } from "maruaudio/contracts"
import { ModuleFaceAccount } from "maruaudio/ui/module-face-controls"
export default function Face(face: AudioModuleFaceProps) {
return (
<header className="my-header">
<h1>MY SYNTH</h1>
{face.appAuth ? (
<ModuleFaceAccount
key={`${face.appAuth.clientId}:${face.appAuth.instanceId}`}
service={face.appAuth}
/>
) : null}
</header>
)
}
The button uses currentColor, so it follows your header's text color in
light and dark designs.
What the button shows:
| State | Button | Menu |
|---|---|---|
| Signed out | Person icon | One line and Sign in with Maru |
| Waiting for the Maru window | Spinner (hover: "Waiting for Maru sign-in…") | The same spinner on the button |
| Just signed in | Check mark, briefly | Account menu |
| Signed in | Person icon with a status dot | Your presets, save field, Plugin page on Maru, More from this creator, Connected apps, Sign out |
4. Use the account in your own controls
face.appAuth is a plain object. For example, a save button that asks for
sign-in only when it is needed:
import { useState } from "react"
import type { AudioModuleFaceProps } from "maruaudio/contracts"
export function SaveToAccount({ face }: { face: AudioModuleFaceProps }) {
const account = face.appAuth
const [note, setNote] = useState("")
if (!account) return null // this host has no accounts
return (
<div>
<button
onClick={async () => {
try {
if (!account.subject) await account.signIn()
await account.savePreset("My sound")
setNote("Saved to your Maru account.")
} catch {
setNote("Not saved. Try again.")
}
}}
>
Save to my account
</button>
<span role="status">{note}</span>
</div>
)
}
| Member | What it does |
|---|---|
subject | null when signed out; otherwise a stable id for this person in your app only. It is not their Maru account id and differs between apps. |
signIn() | Opens Maru's sign-in window. Resolves when the person has connected; rejects if they close it. Call it from a click. |
signOut() | Signs out of your module on this host. |
listPresets() | The person's saved presets for this module: { id, name }[]. |
savePreset(name) | Saves the module's current sound. The host captures the state; you pass only a name. |
loadPreset(id) | Restores a saved preset through the host. |
pages, openPage(page) | The Maru pages this host can open: "module", "creator", "connections". You name a page; the host opens its real address in a new tab. |
A new object arrives whenever the account changes, so read face.appAuth from
props on each render rather than storing it.
Connecting automatically
The first time someone signs in, Maru shows a consent page with your app's name and what it may do. For an app that only signs in and saves presets, that page also offers Connect automatically next time. If they turn it on, later sign-ins go straight back to your module. They can turn this off in Connected apps with Ask every time, or disconnect your app entirely.
It is never offered when the app can spend credits or activate licenses, or
when sign-in returns to a local address, such as a development server on
localhost.
Rules the host enforces
- Your face never receives a password, token, cookie, email or account id, and cannot read them from the host.
- The face cannot open arbitrary links. It names a page; the host decides the address.
- One enabled app per module. With none, or with several, hosts offer no sign-in.
- Every request is checked against the current app and signed-in person, so a stale or forged request from the face is ignored.
Running outside Maru
If your product runs on your own site, backend or app instead of a Maru host, you run the sign-in yourself; see Maru sign-in in your own app. On Maru, none of that is needed.
Related
- Add Maru sign-in to your product: what people see when they connect, and app permissions.
- Generation in an instrument:
face.appGeneration, which uses the same app. - Module UI:
FaceModuleNavbarand the rest of the face kit.