@gbg/go-bridge-web
v0.2.1
Published
Host-page SDK for embedding GBG GO identity verification journeys in a web app via iframe. Framework-agnostic core with a type-safe bridge messaging protocol.
Downloads
965
Readme
@gbg/go-bridge-web
Host-page SDK for embedding GBG GO identity
verification journeys in a web application via iframe. Framework-agnostic,
zero dependencies, with a typed bridge messaging protocol over
postMessage.
Using React? @gbg/go-bridge-web-react
wraps this package with a Provider and hooks.
Prerequisites
You need a GBG GO agreement and a configured journey. The URL you load into the iframe — and therefore the origin you allowlist — comes from your GBG GO journey configuration; your GBG integration contact can provide it, along with the set of bridge actions your specific journey sends.
Installation
npm install @gbg/go-bridge-webQuick start
<iframe
id="journey"
src="https://your-journey-url.example/…"
sandbox="allow-scripts allow-same-origin allow-forms"
allow="camera"
></iframe>import { BridgeHost } from '@gbg/go-bridge-web';
const iframe = document.querySelector<HTMLIFrameElement>('#journey');
if (!iframe) throw new Error('journey iframe not found');
const host = new BridgeHost({
iframe,
// Origins the journey is served from. Required, must be non-empty.
allowedOrigins: ['https://your-journey-origin.example'],
// Identifies your host app in capability.query responses.
hostVersion: 'my-app-1.0.0',
// Capabilities your page offers the journey (all optional).
capabilities: {
'analytics.forward': { supported: true, version: '1.0' },
},
});
// Handle requests the journey sends to your page.
host.registerHandler('analytics.forward', {
handle: (request, responder) => {
const { data } = request.payload as { data: Record<string, unknown> };
myTelemetry.track(data); // your analytics client — not part of the SDK
responder.success({});
},
});
// When the host is no longer needed:
host.detach();Two things to know before your first send:
- Constructing the host appends bridge parameters to the iframe's
src(see Security notes); ifsrcwas already set, this reloads the iframe once. - Outbound messages are not queued. A send before
iframe.srcis set is dropped with a console warning; a send aftersrcis set but before the journey has finished loading can be silently discarded by the browser. Send events only once the journey is running — e.g. after its first event reaches you:
let journeyReady = false;
host.delegate = {
onMessage: (h, message) => {
if (message.type === 'event' && !journeyReady) {
journeyReady = true;
h.sendEvent('host.locale.update', { locale: 'en-GB' });
}
},
};How it works
The SDK exchanges JSON messages with the embedded journey over
window.postMessage. Every message carries a protocol version, a
correlationId, a type, and a payload:
request(journey → host) — the journey asks the host to do something, e.g. forward an analytics event. The host answers via aresponsewith the samecorrelationId.response(host → journey) — terminal statuses aresuccess,error,cancelled, andunsupported;acknowledgedis an interim status for long-running work.event(either direction) — fire-and-forget notifications such as journey lifecycle changes or host locale updates. SeeBridgeActionsfor the well-known action identifiers.
Messages arriving with a different protocol version are processed anyway,
with a console warning. The current version is exported as
BridgeHost.PROTOCOL_VERSION.
Capability discovery
The journey discovers what your page supports by sending a
capability.query request. The SDK answers this automatically from the
capabilities map you pass to the constructor plus anything you add later
with registerCustomCapability(id, version?, handler?) — for ids present
in both, the constructor map takes precedence. Well-known capability
identifiers are exported as CAPABILITY_IDS (camera.document,
camera.selfie, nfc.read, biometric.auth, device.attestation,
location.gps, storage.secure, analytics.forward).
Note: only the supported, version, and permissionState fields of a
CapabilityInfo are transmitted to the journey; constraints is
host-side only.
Handling requests
Which request actions (if any) a journey sends depends on how that
journey is configured — confirm the set with your GBG integration
contact. Register a handler per action with
registerHandler(action, handler). A handler receives the raw
BridgeMessage and a BridgeResponder:
host.registerHandler('camera.document.capture', {
handle: async (request, responder) => {
responder.acknowledge(); // interim: tell the journey work has started
try {
// captureDocument() is your implementation — the SDK does not
// provide capture UI.
const result = await captureDocument();
responder.success({ imageRef: result.ref });
} catch (err) {
responder.error({
code: 'INTERNAL_ERROR',
message: String(err),
recoverable: false,
});
}
},
});A responder sends at most one terminal response — after
success / error / cancelled / unsupported, further calls are
no-ops. If a handler throws (or its returned promise rejects) before
responding, the SDK sends an error response on its behalf (code
HANDLER_FAILURE, which journeys surface as an internal error) so the
journey isn't left waiting.
Requests with no registered handler are queued on host.pendingRequests
(up to 50; beyond that they are dropped and reported via the delegate's
onError) and surfaced through the delegate's onUnhandledRequest; you
can answer a queued request later with
host.respond({ correlationId, status, data }).
Watching traffic and errors
Pass a BridgeHostDelegate to observe traffic and failures:
const host = new BridgeHost({
iframe,
allowedOrigins: ['https://your-journey-origin.example'],
delegate: {
onMessage: (host, message) => console.debug('in', message),
onMessageSent: (host, message) => console.debug('out', message),
onUnhandledRequest: (host, request) => console.warn('unhandled', request),
onError: (host, error) => console.error(error),
},
});onError fires for handler throws (before a response has been sent),
delegate throws, and postMessage exceptions. Sends dropped for other
reasons (no src yet, origin not allowlisted) surface as console
warnings only, and sends after detach() are dropped silently.
host.receivedMessages (the last 200 inbound messages),
host.pendingRequests, and host.lastError expose the same information
for polling-style debugging. Note the buffering: message payloads stay in
your page's memory until trimmed or the host is released.
Security notes
allowedOriginsis your trust boundary. Inbound messages from any other origin are ignored, and outbound sends verify the iframe's currentsrcorigin against the allowlist — so a repointed iframe cannot receive bridge payloads. Entries must behttp(s)origins; wildcards such as*are rejected at construction, and URL-shaped entries are normalized down to their origin.- Sandbox the iframe yourself. The SDK does not set
sandboxattributes.sandbox="allow-scripts allow-same-origin allow-forms"is a reasonable starting point. Because the journey is served cross-origin,allow-same-originrelaxes the sandbox only to the journey's origin (which needs its own storage and cookies), not to yours. Addallow-modals/allow-popupsonly if your journey needs them. - Grant camera access. Journeys that capture documents or selfies in
the browser call
getUserMediainside the iframe, which a cross-origin iframe can only do with a Permissions-Policy grant: setallow="camera"on the iframe (andmicrophoneif your journey records audio). Without it, capture fails inside the journey. - URL parameters. When the host attaches, the SDK appends
gbggoSource(SDK name/version and embedding host) andgbggoHostOrigin(your page's origin) query parameters to the iframesrc, so the journey can identify what is embedding it. This happens once per attach, against the current (or first-assigned)src: if you later repoint the iframe yourself, callattach(iframe)again. Don't strip these parameters.
Lifecycle
attach(iframe) — called for you by the constructor — binds the host to
an iframe and starts listening; you can call it again to rebind (pending
state is preserved). Call detach() when you're done: the SDK installs a
window message listener that otherwise keeps the host instance (and
its message buffers) alive for the lifetime of the page.
License
MIT © GBG Group plc
