Yes, we can still create a replica of the Relay button as before by using the Script Extension (Settings – Obsolete Features – Extensions). However, this feature is already obsolete and may be removed in the future.
Here’s an example of the Script Extension I’m referring to. If your application only relies on the Data payload sent by the relay, no further adjustments to your application are necessary.
/* ========================================================================
RELAY SCRIPTEXTENSION
=========================================================================== */
(function RelayExtension() {
'use strict';
const RELAY_ENDPOINT = "https://localhost:7097/relay";
/* Extract API Key (unchanged) */
function extractApiKeyFromDOM() {
try {
for (const s of document.querySelectorAll('script')) {
const txt = s.textContent || '';
const m = txt.match(/API_KEY\s*=\s*['"]([^'"]+)['"]/);
if (m) return m[1];
}
} catch (e) {}
return null;
}
function getToken() {
return window.API_KEY || extractApiKeyFromDOM() || null;
}
/* Allowed pages */
const allowedHandlers = [
'/sales-invoice-view',
'/sales-invoices',
'/credit-note-view',
'/credit-notes',
'/delivery-note-view',
'/delivery-notes'
];
let currentHandler = null;
(function detectPage() {
const path = window.location.pathname || '';
currentHandler = allowedHandlers.find(h => path.includes(h)) || null;
if (!currentHandler) {
console.log('[RelayExt] Not invoice page — extension inactive');
return;
}
console.log('[RelayExt] Running on page:', currentHandler);
})();
if (!currentHandler) return;
/* Request context */
let latestContextData = null;
window.parent.postMessage({ type: 'page-request' }, '*');
window.parent.postMessage({ type: 'context-request' }, '*');
window.addEventListener('message', (ev) => {
if (ev.source !== window.parent) return;
const d = ev.data;
if (!d) return;
if (d.type === 'context-response') {
latestContextData = d.body;
console.log('[RelayExt] Context:', latestContextData);
}
});
/* Insert Relay button next to PDF button */
function injectRelayButton() {
if (document.getElementById('relay-ext-btn')) return;
const pdfBtn = document.querySelector(
".card-header .flex button.btn.group[onclick^='getPdf']"
);
if (!pdfBtn) {
console.warn("[RelayExt] PDF button not found.");
return;
}
const relayBtn = document.createElement('button');
relayBtn.id = 'relay-ext-btn';
relayBtn.textContent = "🚀 Relay";
relayBtn.className = "btn";
relayBtn.style.marginLeft = "6px";
relayBtn.onclick = () => relayFlow();
pdfBtn.insertAdjacentElement("afterend", relayBtn);
console.log("[RelayExt] Relay button added after PDF.");
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", injectRelayButton);
} else {
injectRelayButton();
}
/* Fetch helpers */
async function fetchJSON(url) {
console.log('[RelayExt] Fetch:', url);
const token = getToken();
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
const res = await fetch(url, { headers, credentials: 'include' });
if (!res.ok) throw new Error("HTTP " + res.status);
return await res.json();
}
async function safeFetch(url) {
try { return await fetchJSON(url); }
catch (e) { console.warn('[RelayExt] safeFetch failed:', url, e.message); return null; }
}
/* Invoice endpoint */
function resolveInvoiceEndpoint(ctx) {
const key = ctx.key || ctx.Key;
const type = (ctx.type || '').toLowerCase();
if (type === 'salesinvoice') return `/api3/sales-invoice-form/${key}`;
if (type === 'creditnote') return `/api3/credit-note-form/${key}`;
return null;
}
/* Main Relay Flow */
async function relayFlow() {
const invoiceKey = latestContextData?.key || latestContextData?.Key;
if (!invoiceKey) return alert("No invoice key.");
console.log("[RelayExt] Running relay for:", invoiceKey);
let dictCurrency = {};
let dictBusiness = {};
const dictInvoice = {};
const dictCustomer = {};
const dictInventory = {};
const dictTaxCode = {};
const dictForeignCurrency = {};
const endpoint = resolveInvoiceEndpoint(latestContextData);
const invoice = await fetchJSON(endpoint);
dictInvoice[invoiceKey] = invoice;
const base = await safeFetch('/api3/base-currency-form');
if (base) dictCurrency = base;
const biz = await safeFetch('/api3/business-details-form');
if (biz) dictBusiness = biz;
let customerObj = null;
if (invoice.customer) {
customerObj = await safeFetch(`/api3/customer-form/${invoice.customer}`);
if (customerObj) dictCustomer[invoice.customer] = customerObj;
}
const curId = customerObj?.currency || customerObj?.Currency;
if (curId) {
const fc = await safeFetch(`/api3/foreign-currency-form/${curId}`);
if (fc) dictForeignCurrency[curId] = fc;
}
if (Array.isArray(invoice.lines)) {
for (const line of invoice.lines) {
if (line.item) {
for (const k of [
'/api3/inventory-item-form/',
'/api3/non-inventory-item-form/',
'/api3/inventory-kit-form/'
]) {
const it = await safeFetch(k + line.item);
if (it) { dictInventory[line.item] = it; break; }
}
}
if (line.taxCode) {
const tc = await safeFetch(`/api3/tax-code-form/${line.taxCode}`);
if (tc) dictTaxCode[line.taxCode] = tc;
}
}
}
function toPascalCaseKeys(obj) {
if (Array.isArray(obj)) {
return obj.map(toPascalCaseKeys);
}
if (obj !== null && typeof obj === "object") {
const newObj = {};
for (const key of Object.keys(obj)) {
// GUID key → do NOT pascal-case
const isGuid = /^[0-9a-fA-F-]{36}$/.test(key);
const newKey = isGuid
? key // leave GUID as-is
: key.charAt(0).toUpperCase() + key.slice(1);
newObj[newKey] = toPascalCaseKeys(obj[key]);
}
return newObj;
}
return obj;
}
const finalPayload = {
BaseCurrency: dictCurrency,
BusinessDetails: dictBusiness,
SalesInvoice: dictInvoice,
Customer: dictCustomer,
InventoryItem: dictInventory,
TaxCode: dictTaxCode,
ForeignCurrency: dictForeignCurrency
};
console.log("[RelayExt] FINAL PAYLOAD:", finalPayload);
/* Build fields */
const Referrer = window.location.href;
const Api = window.location.origin + "/api2";
const Token = getToken() || "";
let View = "";
const iframe = document.querySelector("#nonBatchView iframe");
if (iframe) View = iframe.getAttribute("srcdoc") || "";
/* POST via form — SAME TAB */
const form = document.createElement("form");
form.method = "POST";
form.action = RELAY_ENDPOINT;
form.target = "_self";
function add(name, value) {
const i = document.createElement("input");
i.type = "hidden";
i.name = name;
i.value = value;
form.appendChild(i);
}
add("Referrer", Referrer);
add("Api", Api);
add("Key", invoiceKey);
add("Token", Token);
add("View", View);
const finalPayloadPascal = toPascalCaseKeys(finalPayload);
add("Data", JSON.stringify(finalPayloadPascal));
document.body.appendChild(form);
form.submit();
form.remove();
console.log("[RelayExt] Relay sent via form SAME TAB.");
}
})();