Skip to content

Instantly share code, notes, and snippets.

@dvygolov
Last active May 8, 2026 10:27
Show Gist options
  • Select an option

  • Save dvygolov/9bfad72e5046d44cb65c00f10b4d1d7d to your computer and use it in GitHub Desktop.

Select an option

Save dvygolov/9bfad72e5046d44cb65c00f10b4d1d7d to your computer and use it in GitHub Desktop.
This script helps to share meta pixel to an ad account in a business manager when you have problems with sharing using standart methods
function bmPixelShareManagerApp() {
const APP_ID = "bm-pixel-share-ui";
const STYLE_ID = `${APP_ID}-style`;
const GRAPH_VERSION = "v22.0";
const INTERNAL_GRAPHQL_ENDPOINT = "/api/graphql/?_callFlowletID=0&_triggerFlowletID=1&qpl_active_e2e_trace_ids=";
const INTERNAL_GRAPHQL_CALLER = "RelayModern";
const INTERNAL_PIXEL_LIST_QUERY = {
friendlyName: "BusinessCometBizSuiteSettingsEventsDatasetRootQuery",
docId: "27075127438747082",
};
const INTERNAL_CONNECTED_ASSETS_QUERY = {
friendlyName: "BizKitSettingsAssetToAssetConnectionListContainerQuery",
docId: "25989701040671987",
};
const INTERNAL_REMOVE_CONNECTED_ASSET_MUTATION = {
friendlyName: "BizKitSettingsRemoveAssetToAssetConnectionMutation",
docId: "23922031614068524",
};
const DEFAULT_AD_ACCOUNT_STATUSES = [
"ACTIVE",
"DISABLED",
"IN_GRACE_PERIOD",
"PENDING_CLOSURE",
"PENDING_RISK_REVIEW",
"PENDING_SETTLEMENT",
"UNSETTLED",
];
const BUSINESS_URL = (businessId) =>
`https://business.facebook.com/latest/settings/ad_accounts/?business_id=${encodeURIComponent(
businessId
)}`;
const previousRoot = document.getElementById(APP_ID);
if (previousRoot) {
previousRoot.remove();
}
const previousStyle = document.getElementById(STYLE_ID);
if (previousStyle) {
previousStyle.remove();
}
const state = {
accessToken: "",
businessId: "",
businesses: [],
pixels: [],
accounts: [],
selectedPixelId: "",
selectedAccountId: "",
connectedAssets: [],
loadingBusinesses: false,
loadingContext: false,
loadingConnectedAssets: false,
sharing: false,
removingAssetIds: [],
status: { type: "info", text: "Initializing..." },
lastShare: null,
pixelMappingError: "",
connectedAssetsError: "",
};
let asyncGraphqlTemplatePromise = null;
let connectedAssetsLoadToken = 0;
const style = document.createElement("style");
style.id = STYLE_ID;
style.textContent = `
#${APP_ID} {
position: fixed;
inset: 0;
z-index: 2147483647;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background:
radial-gradient(circle at top left, rgba(20, 157, 221, 0.18), transparent 34%),
radial-gradient(circle at bottom right, rgba(30, 92, 184, 0.18), transparent 42%),
rgba(10, 18, 35, 0.48);
backdrop-filter: blur(12px);
color: #10203a;
font-family: "Segoe UI", "Helvetica Neue", sans-serif;
}
#${APP_ID} * {
box-sizing: border-box;
}
#${APP_ID} .bmps-card {
width: min(560px, 100%);
max-height: min(88vh, 860px);
overflow: auto;
border-radius: 28px;
padding: 28px;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(245, 248, 255, 0.97));
box-shadow:
0 30px 80px rgba(10, 22, 48, 0.28),
inset 0 1px 0 rgba(255, 255, 255, 0.85);
border: 1px solid rgba(116, 146, 199, 0.24);
}
#${APP_ID} .bmps-header {
display: flex;
gap: 16px;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 20px;
}
#${APP_ID} .bmps-title {
margin: 0;
font-size: 24px;
line-height: 1.15;
font-weight: 700;
letter-spacing: -0.03em;
color: #11264a;
}
#${APP_ID} .bmps-subtitle {
margin: 8px 0 0;
font-size: 13px;
line-height: 1.5;
color: #5d708f;
}
#${APP_ID} .bmps-close {
width: 38px;
height: 38px;
border: 0;
border-radius: 999px;
background: rgba(17, 38, 74, 0.07);
color: #11264a;
font-size: 20px;
cursor: pointer;
transition: transform 120ms ease, background 120ms ease;
}
#${APP_ID} .bmps-close:hover {
transform: scale(1.04);
background: rgba(17, 38, 74, 0.12);
}
#${APP_ID} .bmps-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 18px;
}
#${APP_ID} .bmps-chip {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 12px;
border-radius: 999px;
background: rgba(21, 72, 151, 0.07);
color: #21497d;
font-size: 12px;
font-weight: 600;
}
#${APP_ID} .bmps-panel {
padding: 18px;
border-radius: 22px;
background: linear-gradient(180deg, rgba(245, 249, 255, 0.92), rgba(238, 244, 252, 0.92));
border: 1px solid rgba(142, 167, 214, 0.24);
margin-bottom: 16px;
}
#${APP_ID} .bmps-panel-title {
margin: 0 0 6px;
font-size: 15px;
font-weight: 700;
color: #17345e;
}
#${APP_ID} .bmps-panel-text {
margin: 0;
font-size: 13px;
line-height: 1.55;
color: #5d708f;
}
#${APP_ID} .bmps-grid {
display: grid;
gap: 14px;
}
#${APP_ID} .bmps-field {
display: grid;
gap: 8px;
}
#${APP_ID} .bmps-select-shell {
position: relative;
}
#${APP_ID} .bmps-select-shell::after {
content: "";
position: absolute;
top: 50%;
right: 18px;
width: 10px;
height: 10px;
border-right: 2px solid #6d84ad;
border-bottom: 2px solid #6d84ad;
transform: translateY(-65%) rotate(45deg);
pointer-events: none;
transition: transform 120ms ease, border-color 120ms ease;
}
#${APP_ID} .bmps-select-shell:focus-within::after {
border-color: #1d67d6;
transform: translateY(-35%) rotate(225deg);
}
#${APP_ID} .bmps-label {
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #5470a1;
}
#${APP_ID} .bmps-select,
#${APP_ID} .bmps-button {
width: 100%;
min-height: 50px;
border-radius: 16px;
border: 1px solid rgba(100, 130, 188, 0.28);
font-size: 14px;
}
#${APP_ID} .bmps-select {
appearance: none;
padding: 0 52px 0 18px;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(244, 248, 255, 0.98));
color: #213756;
outline: none;
font-weight: 500;
cursor: pointer;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.92),
0 8px 20px rgba(41, 88, 166, 0.08);
transition: border-color 120ms ease, box-shadow 120ms ease, background 120ms ease;
}
#${APP_ID} .bmps-select:focus {
border-color: rgba(29, 103, 214, 0.6);
box-shadow:
0 0 0 4px rgba(29, 103, 214, 0.12),
0 10px 22px rgba(41, 88, 166, 0.12);
}
#${APP_ID} .bmps-select:hover:not(:disabled) {
background:
linear-gradient(180deg, rgba(255, 255, 255, 1), rgba(240, 246, 255, 1));
}
#${APP_ID} .bmps-select:disabled {
cursor: wait;
}
#${APP_ID} .bmps-actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-top: 4px;
}
#${APP_ID} .bmps-button {
border: 0;
cursor: pointer;
padding: 0 18px;
font-weight: 700;
transition: transform 120ms ease, opacity 120ms ease, filter 120ms ease;
}
#${APP_ID} .bmps-button:hover:not(:disabled) {
transform: translateY(-1px);
filter: saturate(1.05);
}
#${APP_ID} .bmps-button:disabled {
opacity: 0.55;
cursor: wait;
}
#${APP_ID} .bmps-button-primary {
background: linear-gradient(135deg, #1677ff, #0f56c9);
color: #fff;
box-shadow: 0 12px 28px rgba(15, 86, 201, 0.24);
}
#${APP_ID} .bmps-button-secondary {
background: rgba(15, 86, 201, 0.08);
color: #17427a;
}
#${APP_ID} .bmps-status {
display: flex;
gap: 10px;
align-items: flex-start;
padding: 14px 16px;
border-radius: 18px;
font-size: 13px;
line-height: 1.5;
margin-top: 16px;
border: 1px solid transparent;
}
#${APP_ID} .bmps-status-info {
background: rgba(17, 119, 255, 0.08);
color: #134589;
border-color: rgba(17, 119, 255, 0.12);
}
#${APP_ID} .bmps-status-success {
background: rgba(28, 156, 98, 0.1);
color: #0d6a3f;
border-color: rgba(28, 156, 98, 0.16);
}
#${APP_ID} .bmps-status-error {
background: rgba(211, 60, 78, 0.1);
color: #9c2438;
border-color: rgba(211, 60, 78, 0.16);
}
#${APP_ID} .bmps-list {
display: grid;
gap: 10px;
margin-top: 14px;
}
#${APP_ID} .bmps-business {
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
align-items: center;
padding: 14px 16px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.78);
border: 1px solid rgba(140, 165, 208, 0.22);
}
#${APP_ID} .bmps-business-name {
margin: 0 0 3px;
font-size: 14px;
font-weight: 700;
color: #123258;
}
#${APP_ID} .bmps-business-id {
margin: 0;
font-size: 12px;
color: #6480ab;
}
#${APP_ID} .bmps-connected-panel {
padding: 16px 18px;
border-radius: 20px;
background: rgba(246, 250, 255, 0.9);
border: 1px solid rgba(142, 167, 214, 0.22);
}
#${APP_ID} .bmps-connected-head {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: baseline;
margin-bottom: 12px;
}
#${APP_ID} .bmps-connected-count {
font-size: 12px;
font-weight: 700;
color: #5875a7;
}
#${APP_ID} .bmps-connected-list {
display: grid;
gap: 8px;
}
#${APP_ID} .bmps-connected-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 11px 13px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.86);
border: 1px solid rgba(124, 151, 202, 0.18);
color: #1f385b;
font-size: 13px;
line-height: 1.4;
}
#${APP_ID} .bmps-connected-main {
min-width: 0;
flex: 1;
}
#${APP_ID} .bmps-connected-actions {
display: inline-flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
#${APP_ID} .bmps-connected-item--selected {
border-color: rgba(24, 112, 231, 0.36);
box-shadow: 0 0 0 3px rgba(24, 112, 231, 0.08);
}
#${APP_ID} .bmps-connected-badge {
padding: 4px 8px;
border-radius: 999px;
background: rgba(24, 112, 231, 0.1);
color: #1655ab;
font-size: 11px;
font-weight: 700;
white-space: nowrap;
}
#${APP_ID} .bmps-connected-remove {
min-width: 84px;
min-height: 34px;
padding: 0 12px;
border: 1px solid rgba(203, 74, 95, 0.22);
border-radius: 999px;
background: rgba(203, 74, 95, 0.08);
color: #a12c3f;
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease, color 120ms ease, opacity 120ms ease;
}
#${APP_ID} .bmps-connected-remove:hover:not(:disabled) {
background: rgba(203, 74, 95, 0.12);
border-color: rgba(203, 74, 95, 0.34);
}
#${APP_ID} .bmps-connected-remove:disabled {
opacity: 0.58;
cursor: wait;
}
#${APP_ID} .bmps-inline-note {
margin: 4px 0 0;
font-size: 12px;
line-height: 1.5;
color: #5f7393;
}
#${APP_ID} .bmps-inline-note-error {
color: #9c2438;
}
#${APP_ID} .bmps-inline-note-success {
color: #0d6a3f;
}
#${APP_ID} .bmps-footer-note {
margin-top: 14px;
font-size: 12px;
line-height: 1.5;
color: #687ca1;
}
#${APP_ID} .bmps-bookmark-link {
display: block;
margin-top: 10px;
font-size: 12px;
line-height: 1.4;
color: #215ec7;
text-decoration: underline;
text-align: center;
cursor: pointer;
}
@media (max-width: 640px) {
#${APP_ID} {
padding: 14px;
}
#${APP_ID} .bmps-card {
padding: 18px;
border-radius: 22px;
}
#${APP_ID} .bmps-business {
grid-template-columns: 1fr;
}
#${APP_ID} .bmps-connected-head {
flex-direction: column;
align-items: flex-start;
}
#${APP_ID} .bmps-connected-item {
align-items: flex-start;
flex-direction: column;
}
#${APP_ID} .bmps-connected-actions {
width: 100%;
justify-content: space-between;
}
}
`;
document.documentElement.appendChild(style);
const root = document.createElement("div");
root.id = APP_ID;
document.body.appendChild(root);
const escapeHtml = (value) =>
String(value ?? "")
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
const setStatus = (type, text) => {
state.status = { type, text };
render();
};
const closeOverlay = () => {
root.remove();
style.remove();
};
const copyScriptAsBase64Bookmarklet = () => {
try {
const scriptContent = `${bmPixelShareManagerApp.toString()}
window.bmPixelShareManagerApp = bmPixelShareManagerApp;
bmPixelShareManagerApp();`;
const base64Content = btoa(unescape(encodeURIComponent(scriptContent)));
const bookmarkletCode = `javascript:(async()=>{const code=decodeURIComponent(escape(atob("${base64Content}")));const url=URL.createObjectURL(new Blob([code],{type:"text/javascript"}));try{await import(url);}finally{setTimeout(()=>URL.revokeObjectURL(url),15000)}})();`;
const fallbackCopy = () => {
const textArea = document.createElement("textarea");
textArea.value = bookmarkletCode;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
};
if (navigator.clipboard?.writeText) {
navigator.clipboard
.writeText(bookmarkletCode)
.then(() => {
setStatus("success", "Bookmarklet copied to clipboard.");
})
.catch(() => {
fallbackCopy();
setStatus("success", "Bookmarklet copied to clipboard.");
});
return;
}
fallbackCopy();
setStatus("success", "Bookmarklet copied to clipboard.");
} catch (error) {
setStatus("error", `Could not create bookmarklet: ${error.message}`);
}
};
const getCurrentBusinessId = () => {
try {
const url = new URL(window.location.href);
const directMatch = url.searchParams.get("business_id");
if (directMatch) {
return directMatch;
}
} catch {}
const candidates = [window.location.search, window.location.hash.replace(/^#/, "?"), window.location.href];
for (const candidate of candidates) {
const match = String(candidate).match(/[?&#]business_id=(\d+)/);
if (match?.[1]) {
return match[1];
}
}
return "";
};
const getModule = (name) => {
try {
return typeof globalThis.require === "function" ? globalThis.require(name) : null;
} catch {
return null;
}
};
const getCurrentUserId = () => {
const moduleUser = getModule("CurrentUserInitialData");
const runtimeCandidates = [
moduleUser?.USER_ID,
moduleUser?.ACCOUNT_ID,
globalThis.CurrentUserInitialData?.USER_ID,
globalThis.CurrentUserInitialData?.ACCOUNT_ID,
globalThis.Env?.user,
globalThis.__user,
];
for (const candidate of runtimeCandidates) {
if (typeof candidate === "string" && candidate.trim()) {
return candidate.trim();
}
}
const html = document.documentElement.innerHTML;
return (
html.match(/"USER_ID":"(\d+)"/)?.[1] ||
html.match(/"ACCOUNT_ID":"(\d+)"/)?.[1] ||
html.match(/"actorID":"(\d+)"/)?.[1] ||
""
);
};
const getLsdToken = () => {
const moduleLsd = getModule("LSD");
const runtimeCandidates = [
moduleLsd?.token,
globalThis.LSD?.token,
document.querySelector('input[name="lsd"]')?.value,
];
for (const candidate of runtimeCandidates) {
if (typeof candidate === "string" && candidate.trim()) {
return candidate.trim();
}
}
const html = document.documentElement.innerHTML;
return (
html.match(/\["LSD",\[],\{"token":"([^"]+)"/)?.[1] ||
html.match(/name="lsd" value="([^"]+)"/)?.[1] ||
""
);
};
const extractAccessTokenFromPage = () => {
const runtimeCandidates = [
globalThis.__accessToken,
globalThis.accessToken,
globalThis.apiAccessToken,
globalThis.__ACCOUNTS_CENTER_ACCESS_TOKEN__,
];
for (const candidate of runtimeCandidates) {
if (typeof candidate === "string" && candidate.trim()) {
return candidate.trim();
}
}
const html = document.documentElement.innerHTML;
const regexes = [
/(?:apiAccessToken|accessToken)":"(EAAG[^"]+)/,
/(?:apiAccessToken|accessToken)\\":\\"(EAAG[^\\"]+)/,
/"accessToken":"(EAAG[^"]+)/,
/"accessToken"\\s*:\\s*"(EAAG[^"]+)/,
];
for (const regex of regexes) {
const match = html.match(regex);
if (match?.[1]) {
return match[1].replaceAll("\\/", "/");
}
}
throw new Error("Could not find an access token on the current page.");
};
const extractAccessToken = (businessId) => {
const runtimeToken = globalThis.__accessToken;
if (typeof runtimeToken === "string" && runtimeToken.trim()) {
return runtimeToken.trim();
}
if (!businessId) {
throw new Error("Outside Business Manager, __accessToken was not found.");
}
return extractAccessTokenFromPage();
};
const uniqueBy = (items, keyGetter) => {
const map = new Map();
for (const item of items) {
const key = keyGetter(item);
if (!key || map.has(key)) {
continue;
}
map.set(key, item);
}
return [...map.values()];
};
const ensureAccessTokenInUrl = (value) => {
const url = new URL(value, `https://graph.facebook.com/${GRAPH_VERSION}/`);
if (!url.searchParams.get("access_token")) {
url.searchParams.set("access_token", state.accessToken);
}
return url.toString();
};
const graphFetch = async (pathOrUrl, options = {}) => {
const method = options.method || "GET";
const isFullUrl = /^https?:\/\//i.test(pathOrUrl);
const url = isFullUrl
? new URL(ensureAccessTokenInUrl(pathOrUrl))
: new URL(
`https://graph.facebook.com/${GRAPH_VERSION}/${String(pathOrUrl).replace(/^\/+/, "")}`
);
if (!isFullUrl) {
const params = options.params || {};
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== "") {
url.searchParams.set(key, String(value));
}
});
url.searchParams.set("access_token", state.accessToken);
}
const fetchOptions = {
method,
credentials: "include",
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
},
};
if (options.body) {
const body = new URLSearchParams();
Object.entries(options.body).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
body.append(key, String(value));
}
});
fetchOptions.body = body;
}
const response = await fetch(url.toString(), fetchOptions);
const json = await response.json();
if (!response.ok || json?.error) {
throw new Error(json?.error?.message || `Graph API error (${response.status})`);
}
return json;
};
const fetchAllPages = async (path, params = {}) => {
const rows = [];
let next = path;
let nextParams = params;
while (next) {
const payload = await graphFetch(next, { params: nextParams });
rows.push(...(payload?.data || []));
next = payload?.paging?.next || "";
nextParams = {};
}
return rows;
};
const captureAsyncGraphqlTemplate = async (businessId) => {
if (asyncGraphqlTemplatePromise) {
return asyncGraphqlTemplatePromise;
}
asyncGraphqlTemplatePromise = new Promise((resolve, reject) => {
if (typeof globalThis.AsyncRequest !== "function") {
reject(new Error("AsyncRequest is not available on this page."));
return;
}
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
const capturedHeaders = {};
let finished = false;
const cleanup = () => {
XMLHttpRequest.prototype.open = originalOpen;
XMLHttpRequest.prototype.send = originalSend;
XMLHttpRequest.prototype.setRequestHeader = originalSetRequestHeader;
};
const finish = (handler, value) => {
if (finished) {
return;
}
finished = true;
cleanup();
handler(value);
};
XMLHttpRequest.prototype.open = function patchedOpen(method, url) {
this.__bmpsCaptureUrl = url;
return originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.setRequestHeader = function patchedSetRequestHeader(name, value) {
capturedHeaders[name] = value;
return originalSetRequestHeader.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function patchedSend(body) {
if (!finished && String(this.__bmpsCaptureUrl || "").includes("/api/graphql/")) {
finish(resolve, {
url: String(this.__bmpsCaptureUrl || ""),
body: String(body || ""),
headers: { ...capturedHeaders },
});
return;
}
return originalSend.apply(this, arguments);
};
try {
const request = new AsyncRequest();
request.setMethod("POST");
request.setURI("/api/graphql/");
request.setData({
doc_id: "0",
variables: "{}",
__aaid: 0,
__bid: businessId || "0",
});
request.send();
} catch (error) {
finish(reject, error);
return;
}
window.setTimeout(() => {
finish(reject, new Error("Could not capture the internal GraphQL request template."));
}, 3000);
}).catch((error) => {
asyncGraphqlTemplatePromise = null;
throw error;
});
return asyncGraphqlTemplatePromise;
};
const internalGraphqlFetch = async ({ friendlyName, docId, variables, businessId }) => {
const template = await captureAsyncGraphqlTemplate(businessId);
const params = new URLSearchParams(template.body);
const currentUserId = getCurrentUserId();
const lsdToken = getLsdToken() || template.headers["X-FB-LSD"] || "";
const asbdId = template.headers["X-ASBD-ID"] || "359341";
if (!currentUserId) {
throw new Error("Could not determine the current user for the internal GraphQL request.");
}
params.set("av", currentUserId);
params.set("__aaid", "0");
params.set("__bid", businessId);
params.set("fb_api_caller_class", INTERNAL_GRAPHQL_CALLER);
params.set("fb_api_req_friendly_name", friendlyName);
params.set("server_timestamps", "true");
params.set("variables", JSON.stringify(variables));
params.set("doc_id", docId);
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
"X-FB-Friendly-Name": friendlyName,
};
if (lsdToken) {
headers["X-FB-LSD"] = lsdToken;
}
if (asbdId) {
headers["X-ASBD-ID"] = asbdId;
}
const response = await fetch(INTERNAL_GRAPHQL_ENDPOINT, {
method: "POST",
credentials: "include",
headers,
body: params.toString(),
});
const text = await response.text();
const payloadText = text.replace(/^for\s*\(\s*;\s*;\s*\);\s*/, "");
let json;
try {
json = JSON.parse(payloadText);
} catch {
throw new Error("Could not parse the internal GraphQL response.");
}
if (!response.ok || json?.error || json?.errors?.length) {
throw new Error(
json?.errorDescription ||
json?.errorSummary ||
json?.errors?.[0]?.message ||
`Internal GraphQL error (${response.status})`
);
}
return json;
};
const normalizeName = (value) => String(value || "").trim().toLowerCase();
const fetchBusinesses = async () => {
const attempts = [
async () => fetchAllPages("/me/businesses", { fields: "id,name", limit: 200 }),
async () => {
const payload = await graphFetch("/me", { params: { fields: "businesses{id,name}" } });
return payload?.businesses?.data || [];
},
];
const errors = [];
const collected = [];
for (const attempt of attempts) {
try {
collected.push(...(await attempt()));
} catch (error) {
errors.push(error.message);
}
}
const normalized = uniqueBy(
collected
.map((item) => ({
id: String(item?.id || ""),
name: item?.name || "Untitled",
}))
.filter((item) => item.id),
(item) => item.id
);
if (!normalized.length && errors.length) {
throw new Error(errors[0]);
}
return normalized.sort((a, b) => a.name.localeCompare(b.name, "ru"));
};
const fetchBusinessPixels = async (businessId) => {
const edges = ["owned_pixels", "client_pixels", "adspixels"];
const results = [];
const errors = [];
for (const edge of edges) {
try {
const items = await fetchAllPages(`/${businessId}/${edge}`, {
fields: "id,name",
limit: 200,
});
results.push(...items);
} catch (error) {
errors.push(`${edge}: ${error.message}`);
}
}
const normalized = uniqueBy(
results
.map((item) => ({
id: String(item?.id || ""),
name: item?.name || "Untitled",
label: `${item?.name || "Untitled"} (${item?.id || "no ID"})`,
}))
.filter((item) => item.id),
(item) => item.id
);
if (!normalized.length && errors.length === edges.length) {
throw new Error("Could not load pixels for this BM.");
}
return normalized.sort((a, b) => a.name.localeCompare(b.name, "ru"));
};
const fetchBusinessAccounts = async (businessId) => {
const edges = ["owned_ad_accounts", "client_ad_accounts"];
const results = [];
const errors = [];
for (const edge of edges) {
try {
const items = await fetchAllPages(`/${businessId}/${edge}`, {
fields: "id,account_id,name",
limit: 200,
});
results.push(...items);
} catch (error) {
errors.push(`${edge}: ${error.message}`);
}
}
const normalized = uniqueBy(
results
.map((item) => {
const rawId = String(item?.account_id || item?.id || "").replace(/^act_/, "");
const name = item?.name || "Untitled";
return {
id: rawId,
apiId: String(item?.id || ""),
name,
label: `${name} (${rawId || "no ID"})`,
};
})
.filter((item) => item.id),
(item) => item.id
);
if (!normalized.length && errors.length === edges.length) {
throw new Error("Could not load ad accounts for this BM.");
}
return normalized.sort((a, b) => a.name.localeCompare(b.name, "ru"));
};
const fetchInternalPixelAssets = async (businessId) => {
const payload = await internalGraphqlFetch({
friendlyName: INTERNAL_PIXEL_LIST_QUERY.friendlyName,
docId: INTERNAL_PIXEL_LIST_QUERY.docId,
businessId,
variables: {
assetFilters: {
ad_account_statuses: DEFAULT_AD_ACCOUNT_STATUSES,
},
assetTypes: ["EVENTS_DATASET_NEW", "PIXEL"],
businessID: businessId,
count: 200,
shouldCountAdmin: false,
},
});
const edges = payload?.data?.business?.connected_objects?.edges || [];
return uniqueBy(
edges
.map((edge) => {
const node = edge?.node || {};
const name =
edge?.nameColumn?.bizkit_settings_render_strategy_no_business_id?.business_object
?.business_object_name ||
edge?.phoneColumn?.business_object_name ||
"";
const assetId = String(node?.assetID || node?.id || "");
return {
assetId,
assetType: String(node?.assetType || ""),
name: name || "Untitled",
};
})
.filter((item) => item.assetId && item.name),
(item) => item.assetId
).sort((a, b) => a.name.localeCompare(b.name, "ru"));
};
const mergePixelsWithInternalAssets = (pixels, internalAssets) => {
const assetsByName = internalAssets.reduce((map, item) => {
const key = normalizeName(item.name);
if (!key) {
return map;
}
const current = map.get(key) || [];
current.push(item);
map.set(key, current);
return map;
}, new Map());
return pixels.map((pixel) => {
const matches = assetsByName.get(normalizeName(pixel.name)) || [];
const internalMatch = matches.length === 1 ? matches[0] : null;
return {
...pixel,
internalAssetId: internalMatch?.assetId || "",
internalAssetType: internalMatch?.assetType || "",
internalMatchCount: matches.length,
};
});
};
const fetchConnectedAssets = async (businessId, internalAssetId) => {
const payload = await internalGraphqlFetch({
friendlyName: INTERNAL_CONNECTED_ASSETS_QUERY.friendlyName,
docId: INTERNAL_CONNECTED_ASSETS_QUERY.docId,
businessId,
variables: {
businessID: businessId,
fromAssetID: internalAssetId,
toAssetType: "AD_ACCOUNT",
searchTerm: null,
},
});
const edges = payload?.data?.fromAsset?.connectedAssets?.edges || [];
return uniqueBy(
edges
.map((edge) => {
const node = edge?.node || {};
const legacyAccountId = String(node?.legacy_account_id || "");
const fallbackId = String(node?.assetID || "");
const id = legacyAccountId || fallbackId;
const name = node?.assetName || "Untitled";
return {
id,
assetId: fallbackId,
name,
label: `${name} (${id || "no ID"})`,
};
})
.filter((item) => item.id),
(item) => item.id
).sort((a, b) => a.name.localeCompare(b.name, "ru"));
};
const getSelectedPixel = () => state.pixels.find((item) => item.id === state.selectedPixelId) || null;
const getSelectedAccount = () =>
state.accounts.find((item) => item.id === state.selectedAccountId) || null;
const isSelectedAccountConnected = () =>
!!state.selectedAccountId &&
state.connectedAssets.some((item) => item.id === state.selectedAccountId);
const isRemovingConnectedAsset = (assetId) => state.removingAssetIds.includes(String(assetId || ""));
const loadConnectedAssetsForSelectedPixel = async () => {
const pixel = getSelectedPixel();
const loadId = ++connectedAssetsLoadToken;
state.connectedAssets = [];
state.connectedAssetsError = "";
if (!pixel?.id) {
render();
return;
}
if (!pixel.internalAssetId) {
state.loadingConnectedAssets = false;
state.connectedAssetsError =
state.pixelMappingError ||
"Connected assets are not available for this pixel on the current page.";
render();
return;
}
state.loadingConnectedAssets = true;
render();
try {
const connectedAssets = await fetchConnectedAssets(state.businessId, pixel.internalAssetId);
if (loadId !== connectedAssetsLoadToken) {
return;
}
state.connectedAssets = connectedAssets;
state.connectedAssetsError = "";
} catch (error) {
if (loadId !== connectedAssetsLoadToken) {
return;
}
state.connectedAssets = [];
state.connectedAssetsError = error.message;
} finally {
if (loadId !== connectedAssetsLoadToken) {
return;
}
state.loadingConnectedAssets = false;
render();
}
};
const loadBusinessesOnly = async () => {
state.loadingBusinesses = true;
setStatus("info", "Loading business managers...");
try {
state.businesses = await fetchBusinesses();
setStatus(
"success",
state.businesses.length
? "Choose a business manager and open it."
: "No business managers were found for this account."
);
} catch (error) {
setStatus("error", error.message);
} finally {
state.loadingBusinesses = false;
render();
}
};
const loadBusinessContext = async (businessId) => {
state.loadingContext = true;
state.loadingConnectedAssets = false;
state.connectedAssets = [];
state.connectedAssetsError = "";
state.pixelMappingError = "";
state.businessId = businessId;
state.lastShare = null;
setStatus("info", `Loading BM data for ${businessId}...`);
try {
const [businesses, pixels, accounts, internalAssets] = await Promise.all([
fetchBusinesses().catch(() => []),
fetchBusinessPixels(businessId),
fetchBusinessAccounts(businessId),
fetchInternalPixelAssets(businessId).catch((error) => {
state.pixelMappingError = error.message;
return [];
}),
]);
state.businesses = businesses;
state.accounts = accounts;
state.pixels = mergePixelsWithInternalAssets(pixels, internalAssets);
state.selectedPixelId = state.pixels[0]?.id || "";
state.selectedAccountId = accounts[0]?.id || "";
if (!state.pixels.length) {
setStatus("error", "No pixels were found for this BM.");
} else if (!accounts.length) {
setStatus("error", "No ad accounts were found for this BM.");
} else {
setStatus("success", "Data loaded. You can now share a pixel.");
}
await loadConnectedAssetsForSelectedPixel();
} catch (error) {
state.pixels = [];
state.accounts = [];
state.connectedAssets = [];
state.connectedAssetsError = "";
setStatus("error", error.message);
} finally {
state.loadingContext = false;
render();
}
};
const sharePixel = async () => {
if (!state.selectedPixelId || !state.selectedAccountId || !state.businessId) {
setStatus("error", "Select a pixel and an ad account first.");
return;
}
if (isSelectedAccountConnected()) {
setStatus("info", "The selected ad account is already connected to this pixel.");
return;
}
state.sharing = true;
state.lastShare = null;
setStatus("info", "Sending share request...");
try {
const payload = await graphFetch(`/${state.selectedPixelId}/shared_accounts`, {
method: "POST",
body: {
method: "POST",
business: state.businessId,
account_id: state.selectedAccountId,
},
});
const pixel = getSelectedPixel();
const account = getSelectedAccount();
state.lastShare = {
ok: true,
pixel: pixel?.label || state.selectedPixelId,
account: account?.label || state.selectedAccountId,
payload,
};
setStatus(
"success",
`Pixel ${pixel?.label || state.selectedPixelId} was shared to ${
account?.label || state.selectedAccountId
} successfully.`
);
await loadConnectedAssetsForSelectedPixel();
} catch (error) {
state.lastShare = { ok: false, error: error.message };
setStatus("error", error.message);
} finally {
state.sharing = false;
render();
}
};
const removeConnectedAsset = async (assetId) => {
const pixel = getSelectedPixel();
const asset = state.connectedAssets.find((item) => item.id === assetId);
if (!state.businessId || !pixel?.internalAssetId || !asset?.assetId) {
setStatus("error", "Could not determine the connected asset to remove.");
return;
}
if (isRemovingConnectedAsset(asset.id)) {
return;
}
state.removingAssetIds = [...state.removingAssetIds, asset.id];
setStatus("info", `Removing ${asset.label} from ${pixel.label}...`);
render();
try {
await internalGraphqlFetch({
friendlyName: INTERNAL_REMOVE_CONNECTED_ASSET_MUTATION.friendlyName,
docId: INTERNAL_REMOVE_CONNECTED_ASSET_MUTATION.docId,
businessId: state.businessId,
variables: {
businessID: state.businessId,
fromAssetID: pixel.internalAssetId,
toAssetID: asset.assetId,
connectedAssetTypes: ["AD_ACCOUNT", "BUSINESS_RESOURCE_GROUP"],
},
});
setStatus("success", `${asset.label} was removed from ${pixel.label}.`);
await loadConnectedAssetsForSelectedPixel();
} catch (error) {
setStatus("error", error.message);
} finally {
state.removingAssetIds = state.removingAssetIds.filter((item) => item !== asset.id);
render();
}
};
const renderBusinesses = () => {
if (state.loadingBusinesses) {
return `
<div class="bmps-panel">
<h3 class="bmps-panel-title">Business Managers</h3>
<p class="bmps-panel-text">Finding available BMs for this account...</p>
</div>
`;
}
return `
<div class="bmps-panel">
<h3 class="bmps-panel-title">You are not inside a specific BM</h3>
<p class="bmps-panel-text">
Pick a business manager and open it in Business Settings.
</p>
<div class="bmps-list">
${
state.businesses.length
? state.businesses
.map(
(business) => `
<div class="bmps-business">
<div>
<p class="bmps-business-name">${escapeHtml(business.name)}</p>
<p class="bmps-business-id">ID: ${escapeHtml(business.id)}</p>
</div>
<button
class="bmps-button bmps-button-primary"
data-action="open-business"
data-business-id="${escapeHtml(business.id)}"
>
Open
</button>
</div>
`
)
.join("")
: `<div class="bmps-business"><div><p class="bmps-business-name">No results</p><p class="bmps-business-id">No accessible business managers were found.</p></div></div>`
}
</div>
<div class="bmps-actions">
<button class="bmps-button bmps-button-secondary" data-action="reload-businesses">
Refresh list
</button>
</div>
</div>
`;
};
const renderConnectedAssets = () => {
const selectedPixel = getSelectedPixel();
const selectedAccountConnected = isSelectedAccountConnected();
const countLabel =
state.connectedAssets.length === 1 ? "1 account" : `${state.connectedAssets.length} accounts`;
let content = "";
if (!selectedPixel?.id) {
content = `<p class="bmps-inline-note">Select a pixel to load connected assets.</p>`;
} else if (state.loadingConnectedAssets) {
content = `<p class="bmps-inline-note">Loading connected assets...</p>`;
} else if (state.connectedAssetsError) {
content = `<p class="bmps-inline-note bmps-inline-note-error">${escapeHtml(
state.connectedAssetsError
)}</p>`;
} else if (!state.connectedAssets.length) {
content = `<p class="bmps-inline-note">No connected ad accounts were found for this pixel.</p>`;
} else {
content = `
<div class="bmps-connected-list">
${state.connectedAssets
.map(
(asset) => `
<div class="bmps-connected-item ${
asset.id === state.selectedAccountId ? "bmps-connected-item--selected" : ""
}">
<div class="bmps-connected-main">${escapeHtml(asset.label)}</div>
<div class="bmps-connected-actions">
${
asset.id === state.selectedAccountId
? `<div class="bmps-connected-badge">Selected</div>`
: ""
}
<button
class="bmps-connected-remove"
data-action="remove-connected"
data-connected-asset-id="${escapeHtml(asset.id)}"
${isRemovingConnectedAsset(asset.id) ? "disabled" : ""}
>
${isRemovingConnectedAsset(asset.id) ? "Removing..." : "Remove"}
</button>
</div>
</div>
`
)
.join("")}
</div>
`;
}
return `
<div class="bmps-connected-panel">
<div class="bmps-connected-head">
<div class="bmps-panel-title">Connected Assets</div>
<div class="bmps-connected-count">${
state.loadingConnectedAssets ? "Loading..." : countLabel
}</div>
</div>
${content}
${
selectedAccountConnected
? `<p class="bmps-inline-note bmps-inline-note-success">The selected ad account is already connected to this pixel.</p>`
: ""
}
</div>
`;
};
const renderContext = () => {
const pixelOptions = state.pixels.length
? state.pixels
.map(
(pixel) => `
<option value="${escapeHtml(pixel.id)}" ${
pixel.id === state.selectedPixelId ? "selected" : ""
}>
${escapeHtml(pixel.label)}
</option>
`
)
.join("")
: `<option value="">No pixels found</option>`;
const accountOptions = state.accounts.length
? state.accounts
.map(
(account) => `
<option value="${escapeHtml(account.id)}" ${
account.id === state.selectedAccountId ? "selected" : ""
}>
${escapeHtml(account.label)}
</option>
`
)
.join("")
: `<option value="">No accounts found</option>`;
const shareDisabled =
state.sharing ||
state.loadingContext ||
!state.selectedPixelId ||
!state.selectedAccountId ||
isSelectedAccountConnected();
return `
<div class="bmps-meta">
<div class="bmps-chip">ID: ${escapeHtml(state.businessId)}</div>
<div class="bmps-chip">Pixels: ${state.pixels.length}</div>
<div class="bmps-chip">Accounts: ${state.accounts.length}</div>
</div>
<div class="bmps-grid">
<label class="bmps-field">
<span class="bmps-label">Pixel</span>
<div class="bmps-select-shell">
<select class="bmps-select" data-role="pixel-select" ${
state.loadingContext ? "disabled" : ""
}>
${pixelOptions}
</select>
</div>
</label>
${renderConnectedAssets()}
<label class="bmps-field">
<span class="bmps-label">Ad Account</span>
<div class="bmps-select-shell">
<select class="bmps-select" data-role="account-select" ${
state.loadingContext ? "disabled" : ""
}>
${accountOptions}
</select>
</div>
</label>
</div>
<div class="bmps-actions">
<button
class="bmps-button bmps-button-primary"
data-action="share"
${shareDisabled ? "disabled" : ""}
>
${
state.sharing
? "Sharing..."
: isSelectedAccountConnected()
? "Already connected"
: "Share"
}
</button>
<button
class="bmps-button bmps-button-secondary"
data-action="reload-context"
${state.loadingContext ? "disabled" : ""}
>
Refresh data
</button>
</div>
${
state.lastShare?.ok
? `
<div class="bmps-footer-note">
The last action completed successfully.
</div>
`
: ""
}
`;
};
const render = () => {
root.innerHTML = `
<div class="bmps-card">
<div class="bmps-header">
<div>
<h2 class="bmps-title">Pixel Share Manager</h2>
<p class="bmps-subtitle">
${
state.businessId
? "Current mode: inside a specific Business Manager."
: "Current mode: choose a Business Manager first."
}
</p>
</div>
<button class="bmps-close" data-action="close" aria-label="Close">×</button>
</div>
${state.businessId ? renderContext() : renderBusinesses()}
<div class="bmps-status bmps-status-${escapeHtml(state.status.type)}">
<div>${state.status.type === "error" ? "!" : state.status.type === "success" ? "✓" : "i"}</div>
<div>${escapeHtml(state.status.text)}</div>
</div>
<a href="#" class="bmps-bookmark-link" data-action="copy-bookmark">Copy as bookmark</a>
</div>
`;
};
root.addEventListener("click", async (event) => {
if (event.target === root) {
closeOverlay();
return;
}
const target = event.target.closest("[data-action]");
if (!target) {
return;
}
const action = target.getAttribute("data-action");
if (action === "close") {
closeOverlay();
return;
}
if (action === "reload-businesses") {
await loadBusinessesOnly();
return;
}
if (action === "copy-bookmark") {
event.preventDefault();
copyScriptAsBase64Bookmarklet();
return;
}
if (action === "reload-context") {
await loadBusinessContext(state.businessId);
return;
}
if (action === "share") {
await sharePixel();
return;
}
if (action === "remove-connected") {
const assetId = target.getAttribute("data-connected-asset-id");
if (assetId) {
await removeConnectedAsset(assetId);
}
return;
}
if (action === "open-business") {
const businessId = target.getAttribute("data-business-id");
if (businessId) {
window.location.href = BUSINESS_URL(businessId);
}
}
});
root.addEventListener("change", async (event) => {
const target = event.target;
if (target.matches('[data-role="pixel-select"]')) {
state.selectedPixelId = target.value;
await loadConnectedAssetsForSelectedPixel();
return;
}
if (target.matches('[data-role="account-select"]')) {
state.selectedAccountId = target.value;
render();
}
});
const init = async () => {
try {
state.businessId = getCurrentBusinessId();
state.accessToken = extractAccessToken(state.businessId);
render();
if (state.businessId) {
await loadBusinessContext(state.businessId);
} else {
await loadBusinessesOnly();
}
} catch (error) {
setStatus("error", error.message);
}
};
render();
void init();
}
window.bmPixelShareManagerApp = bmPixelShareManagerApp;
bmPixelShareManagerApp();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment