UNPKG

@omnizapi/connect

Version:

Omnizapi SDK for browser applications

1,003 lines (993 loc) • 44 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { Environment: () => Environment, Omnizapi: () => Omnizapi, OmnizapiError: () => OmnizapiError, default: () => index_default }); module.exports = __toCommonJS(index_exports); // src/types/index.ts var OmnizapiError = class extends Error { constructor(code, message) { super(message); this.code = code; this.name = "OmnizapiError"; } }; var Environment = /* @__PURE__ */ ((Environment2) => { Environment2["PRODUCTION"] = "PRODUCTION"; Environment2["STAGING"] = "STAGING"; Environment2["SANDBOX"] = "SANDBOX"; return Environment2; })(Environment || {}); // src/config/constants.ts var environments = { ["PRODUCTION" /* PRODUCTION */]: { api: "https://api.omni.z-api.io", front: "https://app.omni.z-api.io" }, ["STAGING" /* STAGING */]: { api: "https://api.staging.omni.z-api.io", front: "https://app.staging.omni.z-api.io" }, ["SANDBOX" /* SANDBOX */]: { api: "", front: "" } }; var getConstants = ({ environment = "PRODUCTION" /* PRODUCTION */, apiUrl, frontUrl } = {}) => { if (!Object.prototype.hasOwnProperty.call(environments, environment)) { throw new OmnizapiError("INVALID_CONFIGURATION", "Unknown environment."); } const defaults = environments[environment]; const urls = { api: apiUrl != null ? apiUrl : defaults.api, front: frontUrl != null ? frontUrl : defaults.front }; for (const key of ["api", "front"]) { let url; try { url = new URL(urls[key]); } catch (e) { throw new OmnizapiError( "INVALID_CONFIGURATION", `A valid ${key}Url is required for this environment.` ); } if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("?") || url.href.includes("#")) { throw new OmnizapiError( "INVALID_CONFIGURATION", `${key}Url must be an HTTP(S) base URL without credentials, query or fragment.` ); } urls[key] = url.href.replace(/\/+$/, ""); } return urls; }; // src/core/allowed-origin.ts var WILDCARD_LABEL = "omnizapi-wildcard-label"; var SCHEME_SEPARATOR = "://"; function parseAllowedOrigin(entry) { const trimmed = entry.trim(); if (!trimmed) return null; const separator = trimmed.indexOf(SCHEME_SEPARATOR); const scheme = separator < 0 ? "https" : trimmed.slice(0, separator).toLowerCase(); if (scheme !== "https" && scheme !== "http") return null; const authority = separator < 0 ? trimmed : trimmed.slice(separator + SCHEME_SEPARATOR.length); const wildcard = authority.startsWith("*."); const rest = wildcard ? authority.slice(2) : authority; const host = rest.split(/[/\\?#]/, 1)[0]; if (host.includes("*") || host.includes("@")) return null; let url; try { url = new URL(`${scheme}://${wildcard ? `${WILDCARD_LABEL}.` : ""}${rest}`); } catch (e) { return null; } if (url.username || url.password) return null; let hostname = url.hostname; if (wildcard) { const prefix = `${WILDCARD_LABEL}.`; if (!hostname.startsWith(prefix)) return null; hostname = hostname.slice(prefix.length); if (hostname.split(".").filter(Boolean).length < 2) return null; } return { protocol: url.protocol, hostname, port: url.port, wildcard }; } function matchesPattern(pattern, origin) { if (origin.protocol !== pattern.protocol || origin.port !== pattern.port) return false; if (!pattern.wildcard) return origin.hostname === pattern.hostname; return origin.hostname.endsWith(`.${pattern.hostname}`); } function isOriginAllowed(origin, entries) { let parsed; try { parsed = new URL(origin); } catch (e) { return false; } if (parsed.origin === "null") return false; return entries.some((entry) => { const pattern = parseAllowedOrigin(entry); return pattern !== null && matchesPattern(pattern, parsed); }); } // src/core/runtime.ts var runtimeKey = /* @__PURE__ */ Symbol.for("omnizapi.connect.runtime.v1"); function getRuntime(host) { var _a, _b, _c; const target = host; let runtime = target[runtimeKey]; if (!runtime) { runtime = { nativeLoginPending: false, failedFacebookScripts: /* @__PURE__ */ new WeakSet(), erroredFacebookScripts: /* @__PURE__ */ new WeakSet(), facebookAppIds: /* @__PURE__ */ new WeakMap(), facebookInitObservers: /* @__PURE__ */ new WeakMap() }; Object.defineProperty(target, runtimeKey, { configurable: true, value: runtime }); } (_a = runtime.facebookAppIds) != null ? _a : runtime.facebookAppIds = /* @__PURE__ */ new WeakMap(); (_b = runtime.facebookInitObservers) != null ? _b : runtime.facebookInitObservers = /* @__PURE__ */ new WeakMap(); (_c = runtime.erroredFacebookScripts) != null ? _c : runtime.erroredFacebookScripts = /* @__PURE__ */ new WeakSet(); return runtime; } // src/core/facebook-sdk.ts function isReadySdk(sdk) { return !!sdk && !("__buffer" in sdk) && typeof sdk.init === "function" && typeof sdk.login === "function"; } function observeFacebookInit(runtime, fb) { var _a; try { const descriptor = Object.getOwnPropertyDescriptor(fb, "init"); const existing = runtime.facebookInitObservers.get(fb); if (existing) { if ((descriptor == null ? void 0 : descriptor.value) !== existing.init) throw new Error("FB.init was replaced."); return existing; } if (!descriptor || typeof descriptor.value !== "function" || !descriptor.writable && !descriptor.configurable) { throw new Error("FB.init cannot be observed."); } const original = descriptor.value; const observer = { calls: 0, init: function(...args) { observer.calls++; runtime.facebookAppIds.delete(fb); return Reflect.apply(original, this, args); } }; Object.defineProperty(fb, "init", { ...descriptor, value: observer.init }); if (((_a = Object.getOwnPropertyDescriptor(fb, "init")) == null ? void 0 : _a.value) !== observer.init) throw new Error("FB.init was not replaced."); runtime.facebookInitObservers.set(fb, observer); return observer; } catch (e) { runtime.facebookAppIds.delete(fb); throw new OmnizapiError( "INVALID_CONFIGURATION", "The Meta SDK initialization cannot be observed safely. Use the popup flow or reload a fresh page." ); } } function initializeFacebookSdk(host, fb, appId) { var _a; if (host.FB !== fb || !fb || "__buffer" in fb || typeof fb.login !== "function") { throw new OmnizapiError("INVALID_CONFIGURATION", "The Meta SDK is not ready or was replaced before initialization."); } if (typeof appId !== "string" || !/^[0-9]+$/.test(appId)) { throw new OmnizapiError("INVALID_CONFIGURATION", "A numeric Meta appId is required for native login."); } const runtime = getRuntime(host); if (runtime.facebookAppIds.get(fb) === null && (runtime.facebookLoadUncertain || runtime.facebookLoading || typeof host.fbAsyncInit === "function")) { runtime.facebookAppIds.delete(fb); } const initializedAppId = runtime.facebookAppIds.get(fb); if (initializedAppId === void 0 || initializedAppId !== null && !runtime.facebookInitObservers.has(fb)) { runtime.facebookAppIds.delete(fb); throw new OmnizapiError( "INVALID_CONFIGURATION", "The host Meta SDK configuration is unknown. Use the popup flow or a fresh page without a host-initialized SDK for native login." ); } const observer = observeFacebookInit(runtime, fb); if (initializedAppId !== null) { if (initializedAppId !== appId) { throw new OmnizapiError( "INVALID_CONFIGURATION", "The Meta SDK is already initialized for another app. Use the popup flow or reload a fresh page before changing apps." ); } return; } const calls = observer.calls; runtime.facebookAppIds.delete(fb); try { fb.init({ appId, version: "v25.0", autoLogAppEvents: true, xfbml: true }); } catch (e) { throw new OmnizapiError("PROVIDER_ERROR", "The Meta SDK could not be initialized."); } if (observer.calls !== calls + 1 || host.FB !== fb || ((_a = Object.getOwnPropertyDescriptor(fb, "init")) == null ? void 0 : _a.value) !== observer.init) { throw new OmnizapiError("INVALID_CONFIGURATION", "The Meta SDK changed during initialization. Use the popup flow or reload a fresh page."); } runtime.facebookAppIds.set(fb, appId); } function loadFacebookSdk(host, signal) { if (signal.aborted) { return Promise.reject(new OmnizapiError("SDK_LOAD_FAILED", "The Meta SDK load was cancelled.")); } const runtime = getRuntime(host); let loading = runtime.facebookLoadObserver; if (!loading && isReadySdk(host.FB)) { loading = { promise: Promise.resolve(host.FB), consumers: 0 }; } if (!loading) { const shared2 = { consumers: 0, // Register the observer and its consumers before setup can fail synchronously. promise: Promise.resolve().then(() => new Promise((resolve, reject) => { var _a; let settled = false; let script; let ownedScript = false; let freshSdk = false; let bootstrap; let previous; let previousCallback; const finish = (sdk, error, definitiveFailure = false) => { if (settled) return; settled = true; clearTimeout(timer); script == null ? void 0 : script.removeEventListener("load", onLoad); script == null ? void 0 : script.removeEventListener("error", onError); try { if (host.fbAsyncInit === onReady) { if (previous) Object.defineProperty(host, "fbAsyncInit", previous); else Reflect.deleteProperty(host, "fbAsyncInit"); } } catch (e) { } if (error && script) { runtime.failedFacebookScripts.add(script); if (definitiveFailure) runtime.erroredFacebookScripts.add(script); else if (!ownedScript) runtime.facebookLoadUncertain = true; if (ownedScript) { try { script.remove(); } catch (e) { } } } if (runtime.facebookLoadObserver === shared2) delete runtime.facebookLoadObserver; if (sdk) resolve(sdk); else reject(error); }; const onLoad = () => { var _a2; if (settled) return; const sdk = host.FB; if (!sdk) return; if ("__buffer" in sdk) bootstrap = sdk; if (((_a2 = bootstrap == null ? void 0 : bootstrap.__buffer) == null ? void 0 : _a2.opts) != null || host.fbAsyncInit !== onReady || (script == null ? void 0 : script.onload) || runtime.facebookLoading || runtime.facebookLoadUncertain) { freshSdk = false; if (runtime.facebookAppIds.get(sdk) === null) runtime.facebookAppIds.delete(sdk); if (bootstrap && runtime.facebookAppIds.get(bootstrap) === null) runtime.facebookAppIds.delete(bootstrap); } const ready = isReadySdk(sdk); if (freshSdk && runtime.facebookAppIds.get(sdk) == null) { if (!ready) runtime.facebookAppIds.set(sdk, null); else if (!runtime.facebookInitObservers.has(sdk)) { try { observeFacebookInit(runtime, sdk); runtime.facebookAppIds.set(sdk, null); } catch (e) { } } } if (ready) finish(sdk); }; const onError = () => finish(void 0, new OmnizapiError("SDK_LOAD_FAILED", "The Meta SDK could not be loaded."), true); const onReady = () => { if (settled) return; try { previousCallback == null ? void 0 : previousCallback.call(host); } catch (e) { } onLoad(); }; const timer = setTimeout(() => finish(void 0, new OmnizapiError( "SDK_LOAD_FAILED", "The Meta SDK load timed out." )), 30 * 1e3); shared2.cancelIfUnused = () => { if (ownedScript && shared2.consumers === 0) { finish(void 0, new OmnizapiError("SDK_LOAD_FAILED", "The Meta SDK load was cancelled.")); } }; try { previous = Object.getOwnPropertyDescriptor(host, "fbAsyncInit"); previousCallback = typeof host.fbAsyncInit === "function" ? host.fbAsyncInit : void 0; const document = host.document; const byId = document.getElementById("facebook-jssdk"); script = Array.from(document.scripts).find((candidate) => { try { const url = new URL(candidate.src); if (url.origin !== "https://connect.facebook.net" || !/^\/[^/]+\/sdk\.js$/.test(url.pathname)) return false; if (!runtime.failedFacebookScripts.has(candidate)) return true; if (!runtime.erroredFacebookScripts.has(candidate)) runtime.facebookLoadUncertain = true; return false; } catch (e) { return false; } }); if (!script) { script = document.createElement("script"); ownedScript = true; const initialSdk = host.FB; if (initialSdk && "__buffer" in initialSdk) bootstrap = initialSdk; freshSdk = !runtime.facebookLoadUncertain && !runtime.facebookLoading && !previousCallback && (!initialSdk || !!bootstrap && runtime.facebookAppIds.get(initialSdk) === null && ((_a = bootstrap.__buffer) == null ? void 0 : _a.opts) == null); if (!byId) script.id = "facebook-jssdk"; script.src = "https://connect.facebook.net/en_US/sdk.js"; script.crossOrigin = "anonymous"; script.async = true; script.defer = true; } shared2.cancelIfUnused(); if (settled) return; Object.defineProperty(host, "fbAsyncInit", previous && !previous.configurable ? { value: onReady } : { configurable: true, enumerable: true, writable: true, value: onReady }); script.addEventListener("load", onLoad); script.addEventListener("error", onError); onLoad(); if (!settled && ownedScript) document.head.appendChild(script); } catch (e) { finish(void 0, new OmnizapiError("SDK_LOAD_FAILED", "The Meta SDK could not be loaded.")); } })) }; runtime.facebookLoadObserver = loading = shared2; } const shared = loading; return new Promise((resolve, reject) => { let settled = false; shared.consumers++; const complete = (sdk, error) => { var _a; if (settled) return; settled = true; signal.removeEventListener("abort", onAbort); shared.consumers--; if (sdk) resolve(sdk); else reject(error); (_a = shared.cancelIfUnused) == null ? void 0 : _a.call(shared); }; const onAbort = () => complete(void 0, new OmnizapiError("SDK_LOAD_FAILED", "The Meta SDK load was cancelled.")); void shared.promise.then( (sdk) => signal.aborted ? onAbort() : complete(sdk), // A shared observer may belong to another module copy's OmnizapiError class. (error) => complete(void 0, new OmnizapiError("SDK_LOAD_FAILED", error.message)) ); signal.addEventListener("abort", onAbort, { once: true }); if (signal.aborted) onAbort(); }); } // src/core/open-popup.ts var openPopup = (url, title, width = 600, height = 700) => { const host = window; const document = host.document; const screenLeft = host.screenLeft !== void 0 ? host.screenLeft : host.screenX; const screenTop = host.screenTop !== void 0 ? host.screenTop : host.screenY; const innerWidth = host.innerWidth ? host.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : host.screen.width; const innerHeight = host.innerHeight ? host.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : host.screen.height; const left = innerWidth / 2 - width / 2 + screenLeft; const top = innerHeight / 2 - height / 2 + screenTop; return host.open( url, title, `scrollbars=yes, width=${width}, height=${height}, top=${top}, left=${left}` ); }; // src/core/client.ts var TOTAL_TIMEOUT_MS = 10 * 60 * 1e3; var HTTP_TIMEOUT_MS = 30 * 1e3; function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function stringOrNull(value) { return typeof value === "string" && value.trim() ? value : null; } function encodeChannelId(channelId) { if (channelId === "." || channelId === "..") { throw new OmnizapiError("INVALID_OPTIONS", "The channelId cannot be a URL path navigation segment."); } try { return encodeURIComponent(channelId); } catch (e) { throw new OmnizapiError("INVALID_OPTIONS", "The channelId cannot be encoded as a URL path."); } } function isFacebookOrigin(origin) { let url; try { url = new URL(origin); } catch (e) { return false; } return url.protocol === "https:" && (url.hostname === "facebook.com" || url.hostname.endsWith(".facebook.com")); } function isChannelType(value) { return [ "META_MESSENGER", "META_INSTAGRAM", "META_WHATSAPP", "BOT_TELEGRAM", "Z_API_WHATSAPP" ].includes(value); } function isFailureReason(value) { return typeof value === "string" && [ "cancelled", "popup_closed", "not_completed", "invalid_channel", "origin_not_allowed", "unsupported_channel" ].includes(value); } var QR_REQUEST_ID_MAX_LENGTH = 64; function qrCodeAnswer(result) { if (!isRecord(result) || result.value != null && typeof result.value !== "string" || result.connected !== void 0 && typeof result.connected !== "boolean") { return { error: { status: null, message: null } }; } return { connected: result.connected === true, value: typeof result.value === "string" ? result.value : null }; } var QR_ERROR_MESSAGE_LIMIT = 300; function qrCodeError(reason) { const source = isRecord(reason) ? reason : {}; const response = isRecord(source.response) ? source.response : void 0; const data = response && isRecord(response.data) ? response.data : {}; const status = [source.status, response == null ? void 0 : response.status].find( (value) => typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599 ); const candidates = response ? [data.message, data.error, data.detail] : [source.message]; const message = candidates.find((value) => typeof value === "string" && value.trim() !== ""); return { status: status != null ? status : null, message: message ? message.trim().slice(0, QR_ERROR_MESSAGE_LIMIT) : null }; } function failure(channelId, type, reason) { return { success: false, reason, channelId, type, code: null, wabaId: null, phoneId: null }; } function requestJson(url, init, signal) { if (typeof fetch !== "function" || typeof AbortController !== "function") { return Promise.reject(new OmnizapiError( "INVALID_CONFIGURATION", "Fetch and AbortController are required." )); } return new Promise((resolve, reject) => { const controller = new AbortController(); let settled = false; const complete = (value, error) => { if (settled) return; settled = true; clearTimeout(timer); signal == null ? void 0 : signal.removeEventListener("abort", onAbort); controller.abort(); if (error) reject(error); else resolve(value); }; const onAbort = () => complete(void 0, new OmnizapiError( "ABORTED", "The API request was aborted." )); const timer = setTimeout(() => complete(void 0, new OmnizapiError( "TIMEOUT", "The API request timed out." )), HTTP_TIMEOUT_MS); signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true }); if (signal == null ? void 0 : signal.aborted) { onAbort(); return; } const run = async () => { let response; try { response = await fetch(url.href, { ...init, signal: controller.signal }); } catch (e) { complete(void 0, new OmnizapiError("NETWORK_ERROR", "The API request failed.")); return; } if (settled) return; if (!response || typeof response.ok !== "boolean") { complete(void 0, new OmnizapiError("INVALID_RESPONSE", "The API response is invalid.")); return; } if (!response.ok) { complete(void 0, new OmnizapiError("HTTP_ERROR", "The API returned an unsuccessful status.")); return; } try { const data = await response.json(); if (!settled) complete(data); } catch (e) { complete(void 0, new OmnizapiError("INVALID_RESPONSE", "The API did not return valid JSON.")); } }; void run().catch(() => complete(void 0, new OmnizapiError( "NETWORK_ERROR", "The API request failed." ))); }); } var OmnizapiClientImpl = class { constructor(props) { if (!isRecord(props) || !stringOrNull(props.publicKey) || Array.from(props.publicKey).some((character) => { const code = character.charCodeAt(0); return code < 32 || code === 127; }) || props.logo !== void 0 && typeof props.logo !== "string" || props.apiUrl !== void 0 && !stringOrNull(props.apiUrl) || props.frontUrl !== void 0 && !stringOrNull(props.frontUrl)) { throw new OmnizapiError("INVALID_CONFIGURATION", "Valid client properties and a publicKey are required."); } this.publicKey = props.publicKey; this.logo = props.logo; this.constants = getConstants({ environment: props.environment, apiUrl: props.apiUrl, frontUrl: props.frontUrl }); } async getListMessengerPages(channelId, code, signal) { if (!stringOrNull(channelId) || !stringOrNull(code)) { throw new OmnizapiError("INVALID_OPTIONS", "A channelId and authorization code are required."); } if (signal !== void 0 && (!isRecord(signal) || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function" || typeof signal.removeEventListener !== "function")) { throw new OmnizapiError("INVALID_OPTIONS", "The abort signal is invalid."); } if (signal == null ? void 0 : signal.aborted) throw new OmnizapiError("ABORTED", "The API request was aborted."); return requestJson( new URL(`${this.constants.api}/v1/channels/${encodeChannelId(channelId)}/list-messenger-pages`), { method: "POST", body: JSON.stringify({ redirectUri: new URL(`${this.constants.front}/oauth/proxy-messenger`).href, code }), headers: { "Content-Type": "application/json", Authorization: this.publicKey } }, signal ); } async connect(options) { var _a; try { if (typeof window === "undefined" || !window.document || !window.location) { throw new OmnizapiError("BROWSER_REQUIRED", "Connecting requires a browser window."); } const host = window; const runtime = getRuntime(host); if (runtime.activeConnection) { throw new OmnizapiError("CONNECTION_IN_PROGRESS", "A connection is already in progress in this window."); } if (!isRecord(options)) { throw new OmnizapiError("INVALID_OPTIONS", "Connection options are required."); } const { channelId, direct, getQRCode, timeoutMs = TOTAL_TIMEOUT_MS, signal } = options; if (typeof channelId !== "string" || direct !== void 0 && typeof direct !== "boolean" || getQRCode !== void 0 && typeof getQRCode !== "function" || typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647 || signal !== void 0 && (!isRecord(signal) || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function" || typeof signal.removeEventListener !== "function")) { throw new OmnizapiError("INVALID_OPTIONS", "Invalid channelId, direct, getQRCode, timeoutMs or signal option."); } if (signal == null ? void 0 : signal.aborted) throw new OmnizapiError("ABORTED", "The connection was aborted."); if (!channelId.trim()) return failure(channelId, null, "invalid_channel"); const encodedChannelId = encodeChannelId(channelId); if (typeof host.open !== "function" || typeof host.addEventListener !== "function" || typeof host.removeEventListener !== "function" || typeof host.location.origin !== "string" || typeof host.location.href !== "string" || typeof ((_a = host.crypto) == null ? void 0 : _a.getRandomValues) !== "function" || typeof AbortController !== "function" || typeof fetch !== "function") { throw new OmnizapiError("INVALID_CONFIGURATION", "Required browser APIs are unavailable."); } const currentOrigin = host.location.origin; if (currentOrigin === "null") return failure(channelId, null, "origin_not_allowed"); const currentUrl = host.location.href; const frontOrigin = new URL(this.constants.front).origin; return await new Promise((resolve, reject) => { const owner = /* @__PURE__ */ Symbol("omnizapi-connection"); const controller = new AbortController(); const deadline = Date.now() + timeoutMs; let settled = false; let channelType = null; let redirectUri = null; let popup = null; let popupError; let timeout; let popupTimer; let messageListener; let nativeLoginStarted = false; let callbackReceived = false; let qrCodeCall = null; const releaseNativeLogin = () => { if (runtime.nativeLoginOwner === owner) { runtime.nativeLoginPending = false; delete runtime.nativeLoginOwner; } }; const globals = []; const finish = (outcome) => { if (settled) return; settled = true; clearTimeout(timeout); clearInterval(popupTimer); try { signal == null ? void 0 : signal.removeEventListener("abort", onAbort); } catch (e) { } if (messageListener) { try { host.removeEventListener("message", messageListener); } catch (e) { } } try { controller.abort(); } catch (e) { } const ownedPopup = popup; popup = null; try { ownedPopup == null ? void 0 : ownedPopup.close(); } catch (e) { } for (const { name, previous, value } of globals) { try { const descriptor = Object.getOwnPropertyDescriptor(host, name); if (!descriptor || !("value" in descriptor) || descriptor.value !== value) continue; if (previous) Object.defineProperty(host, name, previous); else Reflect.deleteProperty(host, name); } catch (e) { } } if (runtime.activeConnection === owner) delete runtime.activeConnection; if (nativeLoginStarted && (callbackReceived || !(outcome instanceof OmnizapiError) && outcome.success)) { releaseNativeLogin(); } if (outcome instanceof OmnizapiError) reject(outcome); else resolve(outcome); }; const onAbort = () => finish(new OmnizapiError("ABORTED", "The connection was aborted.")); const isLive = () => { if (!settled && (signal == null ? void 0 : signal.aborted)) onAbort(); if (!settled && Date.now() >= deadline) { finish(new OmnizapiError("TIMEOUT", "The connection timed out.")); } return !settled; }; const fail = (reason) => finish(failure(channelId, channelType, reason)); const reservePopup = () => { try { const opened = openPopup("about:blank", "_blank", 600, 740); if (!opened || opened === host) throw new Error("Popup unavailable"); if (!isLive()) { opened.close(); return; } popup = opened; } catch (e) { throw new OmnizapiError("POPUP_BLOCKED", "The connection popup could not be opened."); } }; const watchPopup = () => { if (popupTimer) return; popupTimer = setInterval(() => { if (!isLive()) return; try { if (popup == null ? void 0 : popup.closed) fail("popup_closed"); } catch (e) { finish(new OmnizapiError("PROVIDER_ERROR", "The connection popup could not be monitored.")); } }, 500); }; const unwatchPopup = () => { clearInterval(popupTimer); popupTimer = void 0; }; const succeed = (data) => { if (!isLive() || !channelType) return; finish({ success: true, channelId, type: channelType, code: stringOrNull(data.code), wabaId: stringOrNull(data.wabaId), phoneId: stringOrNull(data.phoneId), coexistence: data.coexistence === true, redirectUri, botToken: stringOrNull(data.botToken), pageId: stringOrNull(data.pageId), accessToken: stringOrNull(data.accessToken) }); }; try { runtime.activeConnection = owner; signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true }); if (!isLive()) return; const bytes = host.crypto.getRandomValues(new Uint8Array(16)); const attemptId = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(""); for (const [name, value] of [ ["__omnizapiPublicKey", this.publicKey], ["__omnizapiCurrentChannel", channelId], ["__omnizapiGetQrCode", getQRCode] ]) { const previous = Object.getOwnPropertyDescriptor(host, name); Object.defineProperty(host, name, previous && !previous.configurable ? { value } : { configurable: true, enumerable: true, writable: true, value }); globals.push({ name, previous, value }); } timeout = setTimeout(() => finish(new OmnizapiError( "TIMEOUT", "The connection timed out." )), timeoutMs); if (!direct) { try { reservePopup(); } catch (error) { popupError = error instanceof OmnizapiError ? error : new OmnizapiError("POPUP_BLOCKED", "The connection popup could not be opened."); } } if (!isLive()) return; const run = async () => { var _a2, _b, _c; const info = await requestJson( new URL(`${this.constants.api}/instances/${encodedChannelId}/sdk-info`), { headers: { Authorization: this.publicKey, "Content-Type": "application/json" } }, controller.signal ); if (!isLive()) return; if (!isRecord(info) || !stringOrNull(info.middleware)) { throw new OmnizapiError("INVALID_RESPONSE", "The channel configuration is invalid."); } const allowOrigins = (_a2 = info.allowOrigins) != null ? _a2 : []; if (!Array.isArray(allowOrigins) || !allowOrigins.every((origin) => typeof origin === "string")) { throw new OmnizapiError("INVALID_RESPONSE", "The channel origins are invalid."); } const middleware = info.middleware; channelType = isChannelType(middleware) ? middleware : null; if (currentOrigin !== frontOrigin && !isOriginAllowed(currentOrigin, allowOrigins)) { fail("origin_not_allowed"); return; } if (!channelType) { fail("unsupported_channel"); return; } if (channelType === "Z_API_WHATSAPP" && !getQRCode) { throw new OmnizapiError("INVALID_OPTIONS", "getQRCode is required to connect a Z_API_WHATSAPP channel."); } const appId = (_b = stringOrNull(channelType === "META_INSTAGRAM" ? info.appInstagramId : info.appId)) != null ? _b : ""; const configId = (_c = stringOrNull(info.appConfig)) != null ? _c : ""; if (channelType.startsWith("META_") && !appId || channelType === "META_WHATSAPP" && !configId) { throw new OmnizapiError("INVALID_RESPONSE", "The channel provider configuration is invalid."); } const nativeWhatsApp = channelType === "META_WHATSAPP" && direct === true; let nativeReady = false; let fb; if (nativeWhatsApp) { unwatchPopup(); if (runtime.nativeLoginPending) { throw new OmnizapiError( "CONNECTION_IN_PROGRESS", "A previous Meta login is incomplete. Reload this window before retrying a native login." ); } fb = await loadFacebookSdk(host, controller.signal); if (!isLive()) return; try { initializeFacebookSdk(host, fb, appId); nativeReady = true; } catch (error) { if (!(error instanceof OmnizapiError && error.code === "INVALID_CONFIGURATION")) throw error; } if (!isLive()) return; } if (nativeReady && fb) { popup == null ? void 0 : popup.close(); popup = null; let code = null; let wabaId = null; let phoneId = null; let coexistence = false; const completeNative = () => { if (!isLive() || !code || !wabaId) return; succeed({ code, wabaId, phoneId, coexistence }); }; messageListener = (event) => { if (!isLive() || !isFacebookOrigin(event.origin)) return; let data = event.data; if (typeof data === "string") { try { data = JSON.parse(data); } catch (e) { return; } } if (!isRecord(data) || data.type !== "WA_EMBEDDED_SIGNUP") return; if (data.event === "CANCEL") { fail("cancelled"); } else if (data.event === "ERROR") { finish(new OmnizapiError("PROVIDER_ERROR", "Meta could not complete the connection.")); } else if (data.event === "FINISH" || data.event === "FINISH_ONLY_WABA" || data.event === "FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING") { if (!isRecord(data.data) || !stringOrNull(data.data.waba_id) || data.data.phone_number_id != null && typeof data.data.phone_number_id !== "string") return; wabaId = stringOrNull(data.data.waba_id); phoneId = stringOrNull(data.data.phone_number_id); coexistence = data.event === "FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING"; completeNative(); } }; host.addEventListener("message", messageListener); runtime.nativeLoginPending = true; runtime.nativeLoginOwner = owner; nativeLoginStarted = true; try { fb.login((response) => { if (callbackReceived) return; callbackReceived = true; if (settled) { releaseNativeLogin(); return; } if (!isLive()) return; if (!isRecord(response) || response.error) { finish(new OmnizapiError("PROVIDER_ERROR", "Meta returned an invalid login response.")); return; } if (response.authResponse == null) { fail("not_completed"); return; } if (!isRecord(response.authResponse) || !stringOrNull(response.authResponse.code)) { fail("not_completed"); return; } code = stringOrNull(response.authResponse.code); completeNative(); }, { config_id: configId, response_type: "code", override_default_response_type: true, extras: { setup: {}, featureType: "whatsapp_business_app_onboarding", sessionInfoVersion: "3" } }); } catch (e) { releaseNativeLogin(); nativeLoginStarted = false; throw new OmnizapiError("PROVIDER_ERROR", "The Meta login could not be started."); } return; } let url; if (channelType === "META_MESSENGER" || channelType === "META_INSTAGRAM") { const messenger = channelType === "META_MESSENGER"; redirectUri = new URL(`${this.constants.front}/oauth/proxy-${messenger ? "messenger" : "instagram"}`).href; url = new URL(messenger ? "https://www.facebook.com/dialog/oauth" : "https://www.instagram.com/oauth/authorize"); url.searchParams.set("client_id", appId); if (messenger) url.searchParams.set("display", "popup"); url.searchParams.set("redirect_uri", redirectUri); url.searchParams.set("response_type", "code"); url.searchParams.set("scope", messenger ? "public_profile,business_management,pages_messaging,pages_show_list,pages_manage_metadata,pages_read_engagement" : "instagram_business_basic,instagram_business_manage_messages"); url.searchParams.set("state", JSON.stringify({ currentUrl, attemptId, channelId, pk: this.publicKey })); } else if (channelType === "META_WHATSAPP") { url = new URL(`${this.constants.front}/oauth/proxy-cloud-api`); url.searchParams.set("configId", configId); url.searchParams.set("appId", appId); url.searchParams.set("channelId", channelId); url.searchParams.set("channelType", channelType); url.searchParams.set("logo", this.logo || new URL(`${this.constants.front}/logos/SVG/Ativo%2016.svg`).href); url.searchParams.set("pk", this.publicKey); } else { url = new URL(`${this.constants.front}/connect/${channelType === "BOT_TELEGRAM" ? "telegram" : "whatsapp-web"}`); url.searchParams.set("channelId", channelId); url.searchParams.set("pk", this.publicKey); if (channelType === "Z_API_WHATSAPP") url.searchParams.set("qr", "message"); } if (channelType !== "META_MESSENGER" && channelType !== "META_INSTAGRAM") { url.searchParams.set("attemptId", attemptId); } const answerQrCodeRequest = (data, target) => { const fetchQrCode = getQRCode; if (channelType !== "Z_API_WHATSAPP" || !fetchQrCode || data.attemptId !== attemptId || data.channelId !== channelId || typeof data.requestId !== "string" || !data.requestId || data.requestId.length > QR_REQUEST_ID_MAX_LENGTH) return; const requestId = data.requestId; const reply = (body) => { if (!isLive() || popup !== target) return; try { target.postMessage({ type: "qr-code-response", attemptId, channelId, requestId, ...body }, frontOrigin); } catch (e) { } }; const call = qrCodeCall != null ? qrCodeCall : qrCodeCall = Promise.resolve().then(() => fetchQrCode()); void call.then( (result) => reply(qrCodeAnswer(result)), (reason) => reply({ error: qrCodeError(reason) }) ).finally(() => { if (qrCodeCall === call) qrCodeCall = null; }); }; messageListener = (event) => { if (!isLive() || !popup || event.origin !== frontOrigin || event.source !== popup) return; const data = event.data; if (isRecord(data) && data.type === "qr-code-request") { answerQrCodeRequest(data, popup); return; } if (!isRecord(data) || data.type !== "after-connect" || data.attemptId !== attemptId || data.channelId !== channelId || typeof data.success !== "boolean" || data.reason !== void 0 && !isFailureReason(data.reason) || data.coexistence !== void 0 && typeof data.coexistence !== "boolean" || ["code", "wabaId", "phoneId", "botToken", "pageId", "accessToken", "redirectUri"].some( (key) => data[key] != null && typeof data[key] !== "string" )) return; if (!data.success) { fail(isFailureReason(data.reason) ? data.reason : "not_completed"); } else if ((channelType == null ? void 0 : channelType.startsWith("META_")) && !stringOrNull(data.code) || channelType === "BOT_TELEGRAM" && !stringOrNull(data.botToken)) { fail("not_completed"); } else { succeed(data); } }; host.addEventListener("message", messageListener); if (!isLive()) return; if (popupError) throw popupError; if (!popup) reservePopup(); if (popup == null ? void 0 : popup.closed) { fail("popup_closed"); return; } watchPopup(); try { popup == null ? void 0 : popup.location.replace(url.href); } catch (e) { throw new OmnizapiError("PROVIDER_ERROR", "The connection popup could not be navigated."); } }; void run().catch((error) => { if (!isLive()) return; finish(error instanceof OmnizapiError ? error : new OmnizapiError( "PROVIDER_ERROR", "The connection could not be completed." )); }); } catch (error) { finish(error instanceof OmnizapiError ? error : new OmnizapiError("INVALID_CONFIGURATION", "The connection could not be initialized.")); } }); } catch (error) { if (error instanceof OmnizapiError) throw error; throw new OmnizapiError("INVALID_OPTIONS", "The connection could not be started with these options."); } } }; var client_default = OmnizapiClientImpl; // src/index.ts var Omnizapi = class { static newClient(props) { return new client_default(props); } }; var index_default = Omnizapi; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Environment, Omnizapi, OmnizapiError });