@omnizapi/connect
Version:
Omnizapi SDK for browser applications
93 lines (82 loc) • 4.4 kB
text/typescript
/**
* Load after <button id="connect-btn" type="button">Connect channel</button>
* and <button id="cancel-btn" type="button" disabled>Cancel authorization</button>
* and <p id="status" role="status"></p>.
* Negative results resolve; technical SDK errors reject. Neither is logged.
* AbortSignal support is an Unreleased change; see the repository changelog.
*/
import Omnizapi, { OmnizapiError } from "@omnizapi/connect";
import type { ConnectionFailureReason, OmnizapiClient, OmnizapiErrorCode } from "@omnizapi/connect";
const failureMessages: Record<ConnectionFailureReason, string> = {
cancelled: "Authorization was cancelled.",
popup_closed: "The popup was closed before authorization finished.",
not_completed: "Authorization was not completed.",
invalid_channel: "This channel is invalid or unavailable.",
origin_not_allowed: "This application origin is not authorized for the channel.",
unsupported_channel: "This channel type is not supported.",
};
const errorMessages: Record<OmnizapiErrorCode, string> = {
BROWSER_REQUIRED: "Open this page in a browser to connect.",
CONNECTION_IN_PROGRESS: "Finish the active attempt. If native Meta login is still awaiting its callback, wait for it or reload before another native attempt.",
INVALID_CONFIGURATION: "Check the public key, environment, base URLs and any conflicting Meta app ID.",
INVALID_OPTIONS: "Check the channel ID, timeout and AbortSignal options.",
NETWORK_ERROR: "The connection service could not be reached. Please try again.",
HTTP_ERROR: "The connection service rejected the request.",
INVALID_RESPONSE: "The connection service returned an invalid response.",
POPUP_BLOCKED: "Allow popups for this application, then click Connect again.",
TIMEOUT: "The operation timed out. Only native Meta login still awaiting its callback blocks a native retry: wait for it or reload.",
ABORTED: "Authorization was stopped by this application. If native Meta login is still awaiting its callback, wait for it or reload before another native attempt.",
SDK_LOAD_FAILED: "The provider SDK could not load. Check browser restrictions.",
PROVIDER_ERROR: "The provider reported an error. Check the provider configuration before retrying.",
};
let client: OmnizapiClient | undefined;
if (typeof document !== "undefined") {
const button = document.querySelector<HTMLButtonElement>("#connect-btn");
const cancelButton = document.querySelector<HTMLButtonElement>("#cancel-btn");
const statusElement = document.querySelector<HTMLElement>("#status");
if (button && cancelButton && statusElement) {
let controller: AbortController | undefined;
cancelButton.addEventListener("click", () => { controller?.abort(); });
button.addEventListener("click", async () => {
if (button.disabled) return;
button.disabled = true;
statusElement.textContent = "Connecting...";
try {
client ??= Omnizapi.newClient({ publicKey: "your-public-key" });
controller = new AbortController();
cancelButton.disabled = false;
const result = await client.connect({
channelId: "your-channel-id",
signal: controller.signal,
});
// Cancel only the SDK authorization, never imply a backend rollback.
controller = undefined;
cancelButton.disabled = true;
if (!result.success) {
statusElement.textContent = failureMessages[result.reason];
return;
}
// Your backend must authorize result.channelId for the session/tenant.
const response = await fetch("/api/connect-channel", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(result),
});
if (!response.ok) {
statusElement.textContent = "The backend could not finalize the connection.";
return;
}
statusElement.textContent = "Channel connected!";
} catch (error) {
// Do not display/log raw errors: messages may contain sensitive URLs.
statusElement.textContent = error instanceof OmnizapiError
? errorMessages[error.code]
: "Could not confirm the connection. Check its backend status before retrying.";
} finally {
controller = undefined;
cancelButton.disabled = true;
button.disabled = false;
}
});
}
}