@omnizapi/connect
Version:
Omnizapi SDK for browser applications
974 lines (966 loc) • 42.9 kB
JavaScript
// 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;
export {
Environment,
Omnizapi,
OmnizapiError,
index_default as default
};