Websites
The web SDK runs in every current browser and needs no framework. It mirrors the tab as page structure (DOM, using rrweb), not as video: this saves bandwidth and keeps text sharp.
Adding the SDK
With a script tag
<script src="https://relyo.de/sdk/relyo.js"></script>
<script>
const relyo = Relyo.init({ relayUrl: 'wss://relyo.de', projectKey: 'pk_yourproject' });
</script>
The script creates the global object Relyo (about 40 kB gzip). Call Relyo.init once per page; a second call returns the same instance.
Two addresses are available:
| Address | Behaviour |
|---|---|
https://relyo.de/sdk/relyo.js | always the latest version, browsers check for changes every time (no-cache) |
https://relyo.de/sdk/v0.1.0/relyo.js | fixed version, cached for one year |
Load only on click
The leanest option: the SDK is only loaded when someone taps “Support”. Until then there is not a single request to relyo.de. This is how the support link on jochen-maurer.com works.
<a href="mailto:[email protected]" id="support">Support</a>
<script>
let relyo;
function loadRelyo() {
relyo ??= new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://relyo.de/sdk/relyo.js';
script.onload = () =>
resolve(
Relyo.init({ relayUrl: 'wss://relyo.de', projectKey: 'pk_yourproject', connect: 'lazy' }),
);
script.onerror = () => {
relyo = undefined;
reject(new Error('Relyo not reachable'));
};
document.head.append(script);
});
return relyo;
}
document.querySelector('#support').addEventListener('click', (event) => {
event.preventDefault();
const fallback = event.currentTarget.href; // fallback if relyo.de cannot be reached
loadRelyo()
.then((r) => r.showCode())
.catch(() => (location.href = fallback));
});
</script>
As a module
relyo.mjs is the same SDK as an ES module and can be loaded from any page:
const { Relyo } = await import('https://relyo.de/sdk/relyo.mjs');
const relyo = Relyo.init({ relayUrl: 'wss://relyo.de', projectKey: 'pk_yourproject' });
There is no npm package yet.
React and Next.js
The SDK needs window and document. In Next.js it therefore belongs in a client component and is only loaded on click, never during server rendering.
'use client';
interface RelyoInstance {
showCode(): void;
}
declare global {
interface Window {
Relyo?: { init(options: Record<string, unknown>): RelyoInstance };
}
}
let relyo: Promise<RelyoInstance> | undefined;
function loadRelyo(): Promise<RelyoInstance> {
relyo ??= new Promise<RelyoInstance>((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://relyo.de/sdk/relyo.js';
script.onload = () =>
window.Relyo
? resolve(
window.Relyo.init({
relayUrl: 'wss://relyo.de',
projectKey: 'pk_yourproject',
connect: 'lazy',
}),
)
: reject(new Error('Relyo not loaded'));
script.onerror = () => reject(new Error('Relyo not reachable'));
document.head.append(script);
}).catch((err: unknown) => {
relyo = undefined;
throw err;
});
return relyo;
}
export function SupportLink() {
return (
<button type="button" onClick={() => loadRelyo().then((r) => r.showCode())}>
Support
</button>
);
}
Navigation inside the app (React Router, Next.js links) needs nothing else: the SDK notices new addresses by itself.
Options
Relyo.init takes an object with these fields:
| Option | Default | Meaning |
|---|---|---|
relayUrl | – | Required. Server address, for example wss://relyo.de; https:// becomes wss://. |
projectKey | – | Your website’s project key. Without a key, a server with the open service assigns the page to its address (origin). |
user | – | Signed-in user, { id, name }. Display only for your supporters, not verified. See Signed-in users. |
connect | 'auto' | When the SDK connects, see Connection. |
redactSelector | – | Additional CSS selector for areas to mask, for example '.account-details, [data-private]'. .relyo-redact always applies. |
maskAllInputs | false | true masks all input fields, not just the sensitive ones. |
consentText | – | Your own explanation in the consent dialog; {agent} is replaced by the supporter’s name. |
cspNonce | – | Nonce for the SDK’s style element if your CSP only allows inline styles with a nonce. |
appVersion | – | Version of your site; shown in the console. |
locale | 'de' | Language of the dialogs. Only German so far. |
Relyo.init returns an instance with these methods:
| Method | Effect |
|---|---|
showCode() | requests a code and shows it in a dialog |
end() | ends a running session or cancels the code or consent |
identify(user) | sets the signed-in user, identify(null) on logout |
destroy() | tears down the SDK; a running session can be resumed by a new init in the same tab |
Connection
connect | Behaviour |
|---|---|
'auto' | with user, connect at once (the device appears in the console); anonymous visitors only on showCode() |
'eager' | always connect at once, even anonymously |
'lazy' | only on showCode(); the connection closes again when the code or session ends |
For a support link in the footer, 'lazy' is the right choice. Before the first connection the SDK stores nothing in the browser, not even an identifier.
Signed-in users
With user (or later identify), the devices of signed-in users appear in the console’s device list. Supporters with their own account can request a session there without the user reading out a code; consent on the device is still required.
relyo.identify({ id: 'u_123', name: 'Max M.' }); // after login
relyo.identify(null); // on logout
The device list is only available for projects with a project key, not with the open service. id and name are for display only; do not send data your supporters should not see.
Masking
Masking happens in the user’s browser before any data leaves it:
- Always: fields with
type="password",type="email"andtype="tel", fields withautocomplete="cc-…"(card data) andautocomplete="one-time-code". .relyo-redact: elements with this class and everything inside. The supporter sees an empty area in their place; input inside arrives as*.redactSelector: works like.relyo-redactfor your own selectors.maskAllInputs: true: all input fields.
<div class="relyo-redact">IBAN: DE12 3456 …</div>
Mask everything with sensitive data that the rules above do not cover, such as health data or ID numbers in ordinary text fields. The SDK rejects remote input into masked fields.
Content Security Policy
If your page has a CSP, Relyo needs:
| Directive | Value | Purpose |
|---|---|---|
script-src | https://relyo.de | the script or module |
connect-src | wss://relyo.de | the connection to the server |
style-src | 'unsafe-inline' or 'nonce-…' with the cspNonce option | the SDK dialogs (in a shadow DOM) |
With your own server, use its address instead of relyo.de.
Flow and page changes
- Consent: transmission only starts after “Share”. The dialog states what is shared (this tab) and what is not.
- Status bar: during the session a bar with the supporter’s name and “End” sits at the top. It cannot be turned off.
- Reloads and links: if the page reloads or the user follows a link to another page with Relyo, the SDK resumes the session in the same tab. The server keeps it open for 30 seconds.
- Control: clicks, input, scrolling and the keys Enter, Escape, Tab and arrows. Any click or key press of the user’s own ends control at once.
Limitations
- Content from third-party iframes (for example embedded videos or payment forms) appears as an empty area.
<video>and<canvas>are not mirrored.- Remote keys do not trigger browser default actions: Tab does not move focus, arrow keys do not scroll. Enter in a form field submits the form.
- The dialogs are in German only so far.
- Images are sent along so that the supporter can see them even behind a login. Pages with many large images need correspondingly more bandwidth.
Error messages
If no code can be requested, the dialog shows a message instead of the code (currently in German):
| Message | Cause |
|---|---|
| “Keine Verbindung zum Support-Server …” | server not reachable, network or CSP blocks it, or too many connections from the same IP address |
| “Gerade sind alle Support-Plätze belegt …” | limit for concurrent sessions reached |
| “Support ist für diese Seite derzeit nicht verfügbar.” | site blocked |
| “Der Support-Code konnte nicht angefordert werden.” | for example an unknown project key or an address that does not match the key |