6.3 KiB
JavaScript SDK
The SDK is a JavaScript library embedded in participating websites. It wraps the browser messaging layer between the page and the extension, exposing a clean API to website developers. Without it, a site could handle Port messaging directly — the SDK makes that unnecessary.
The SDK has no logic of its own. It does not perform cryptography, hold state, or make network requests. It is a thin bridge.
Initialisation
The website developer initialises the SDK with the site's registration certificate, obtained from the LOCQR backend admin panel when registering the domain. The cert is a static value that changes only on renewal.
locqr.init({ cert: 'BASE64_ENCODED_CERT' });
The cert is not embedded in the page DOM. Passing it at initialisation is sufficient; the SDK forwards it to the extension at connect time.
Communication model
The SDK communicates with the extension service worker directly via
chrome.runtime.connect(extensionId) — no content script is involved. This
opens a long-lived bidirectional Port. The extension reads the real page
URL from sender.tab.url (browser-provided; cannot be spoofed by the page).
website code ↔ SDK ↔ chrome.runtime Port ↔ service worker
All SDK↔extension communication for the lifetime of the page uses this Port.
The full Port message protocol (message types, LocqrError, Credential
schemas) is defined in ../interfaces.md.
Activation
After locqr.init(), the SDK opens the Port and sends the cert. The extension
verifies it and responds with the outcome. locqr.ready is a Promise that
resolves with the authorised feature set on success, or rejects with the
verification error.
try {
const { features } = await locqr.ready;
// features: e.g. ['login']
} catch (err) {
// site not registered, cert invalid, or account error
}
The SDK exposes only the methods corresponding to the declared features. A site
whose cert does not include "vault" does not get locqr.encrypt().
API surface
locqr.init(options)
Initialises the SDK. Must be called before any other method.
locqr.init({ cert: string });
locqr.ready → Promise<{ features: string[] }>
Resolves when the extension has verified the site and returned an authorised feature set. Rejects with an error object if the extension is not installed, the site is not registered, the cert is invalid, or the account is not in good standing.
type LocqrError = {
code: 'NOT_INSTALLED' | 'NOT_REGISTERED' | 'CERT_INVALID' | 'ACCOUNT_ERROR' | 'RUN_FAILED';
reason?: string; // sub-reason for CERT_INVALID / ACCOUNT_ERROR / RUN_FAILED
security?: boolean; // true for security-class errors
}
NOT_INSTALLED is the only code produced before a Port is opened. All other
codes arrive from the extension over the Port after the connection succeeds.
locqr.requestCredential() → Promise<Credential>
Signals to the extension that a credential is needed for the current site. Triggers Phase 1 of the run. Resolves when the credential is delivered at the end of Phase 4. Rejects if the run fails or is aborted.
const { username, password } = await locqr.requestCredential();
type Credential = {
username: string;
password: string;
// totp and further fields deferred
}
locqr.on(event, handler) / locqr.off(event, handler)
Registers and removes handlers for extension-initiated events.
| Event | When | Payload |
|---|---|---|
ready |
Verification complete | { features: string[] } |
run:started |
Phase 1 complete; QR displayed | {} |
run:delivered |
Credential delivered | { credential: Credential } |
run:error |
Run failed | { error: LocqrError } |
run:delivered fires alongside the requestCredential() Promise resolving.
Sites using the event model do not need to call requestCredential(). Both
models are valid; mixing them on the same run is not.
GYBBR: vault API (deferred)
For GYBBR, the SDK will expose cryptographic vault operations once the "vault"
feature is declared in the site's cert. The website requests operations by key
ID and receives results; key material never leaves the extension.
Anticipated surface (not yet designed):
const ciphertext = await locqr.encrypt({ keyId: 'k1', data: plaintext });
const plaintext = await locqr.decrypt({ keyId: 'k1', data: ciphertext });
Extension-initiated vault notifications (e.g. identity key available) will be
delivered as events via locqr.on. Exact method signatures and event names are
deferred to the GYBBR design phase.
Static properties
The SDK exposes stable reference values as static properties, available
immediately after the script loads — before locqr.init() and regardless of
whether the extension is installed. Website developers can use these to build
install prompts, help text, or footer links without hardcoding URLs themselves.
locqr.installLinks // { chrome: string, firefox: string, ... }
locqr.mobileLinks // { android: string, ios: string }
locqr.website // LOCQR public website URL
locqr.ready rejecting with code: 'NOT_INSTALLED' is the signal to surface
these links. How they are presented is entirely the website's decision; the SDK
provides the data only. DOM helpers, install-prompt components, etc. are
deferred.
ios within mobileLinks is present as a placeholder but empty until the iOS
app exists.
Distribution
How the SDK is packaged and distributed (npm, CDN script tag, bundled asset)
is deferred. The extension ID baked into the SDK is stable for the lifetime of
the Store-published extension; it changes only on a full re-publication, which
is an extraordinary event. Development builds use a different ID configured via
locqr.init({ cert, extensionId: DEV_ID }).
Security notes
- The SDK never receives key material. It receives only operation results (credentials, ciphertext, plaintext of things the site requested decrypted).
- The real page URL is read by the extension from
sender.tab.url— the page cannot supply or spoof it. - Feature enforcement is in the extension. The SDK surface is a reflection of what the cert permits, not an independent gate.
- Once a credential is passed to the website via
requestCredential(), it has left the LOCQR security boundary. What the site does with it is out of scope.