Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 218 additions & 0 deletions .cloudflare/cloudflare-worker-meta-injector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/**
* Cloudflare Worker - Dynamic Meta Tag Injector for Allways
*
* Deployed on the all-ways.io zone in front of the SPA. Crawlers don't run
* JS, so this rewrites <head> meta tags per route and points og:image at
* the API's dynamic card renderer (das-allways GET /og-image).
*
* Same architecture as gittensor-ui's meta injector; adapted for Allways'
* path-param routes (/swap/:swapId) instead of query-param routes.
*/

// Point both at the matching environment when deploying to the test zone
// (test.all-ways.io -> https://test-api.all-ways.io).
const API_BASE = "https://api.all-ways.io";
const SITE_BASE = "https://all-ways.io";

addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
const url = new URL(request.url);

// Only process HTML requests
const accept = request.headers.get("Accept") || "";
const isHtmlRequest =
accept.includes("text/html") ||
url.pathname === "/" ||
!url.pathname.includes(".");

// Fetch the original response
const response = await fetch(request);

// Only modify HTML responses
if (
!isHtmlRequest ||
!response.headers.get("content-type")?.includes("text/html")
) {
return response;
}

const html = await response.text();
const modifiedHtml = await injectMetaTags(html, url);

return new Response(modifiedHtml, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}

// Mirror of allways-ui/src/utils/format.ts CHAIN_DECIMALS — keep in lockstep.
const CHAIN_DECIMALS = {
btc: { exp: 1e8, digits: 8, symbol: "BTC" },
tao: { exp: 1e9, digits: 4, symbol: "TAO" },
sol: { exp: 1e9, digits: 4, symbol: "SOL" },
};

function formatAmount(raw, chain) {
const config = CHAIN_DECIMALS[(chain || "").toLowerCase()];
const val = parseInt(raw, 10);
if (!config || !Number.isFinite(val)) return null;
const fixed = (val / config.exp).toFixed(config.digits);
const trimmed = fixed.replace(/(\.\d\d[0-9]*?)0+$/, "$1").replace(/\.$/, "");
return `${trimmed} ${config.symbol}`;
}

const STATUS_LABELS = {
ACTIVE: "Active",
FULFILLED: "Fulfilled",
COMPLETED: "Completed",
TIMED_OUT: "Timed out",
};

// Mirror of das-allways og-images formatDurationSecs — full words.
function formatDuration(secs) {
if (!Number.isFinite(secs) || secs < 0) return null;
const unit = (value, name) => `${value} ${name}${value === 1 ? "" : "s"}`;
if (secs < 60) return unit(Math.round(secs), "second");
if (secs < 3600) return unit(Math.round(secs / 60), "minute");
const hours = parseFloat((secs / 3600).toFixed(1));
return `${hours} hour${hours === 1 ? "" : "s"}`;
}

/**
* Inject dynamic meta tags based on URL
*/
async function injectMetaTags(html, url) {
const pathname = url.pathname;

// Fresh render per share while platform caches still apply per-URL.
const cacheBuster = Date.now();

// Just the name: the og image itself says "Any asset to any asset."
let title = "Allways";
let description =
"Choose what you send, what arrives, and where it lands. Delivery guaranteed. Bittensor Subnet 7.";
let image = `${API_BASE}/og-image?type=home&v=${cacheBuster}`;
let imageAlt =
"Allways — any asset to any asset. Universal transaction layer on Bittensor Subnet 7.";

// Swap detail page: /swap/:swapId
const swapMatch = pathname.match(/^\/swap\/(-?\d+)$/);
if (swapMatch) {
const swapId = swapMatch[1];
image = `${API_BASE}/og-image?type=swap&id=${encodeURIComponent(swapId)}&v=${cacheBuster}`;
title = "Swap on Allways";
description =
"Track this swap on Allways, the universal transaction layer on Bittensor Subnet 7.";
imageAlt = "Swap details on Allways.";

// Enrich title/description from the API so text matches the card.
// Any failure keeps the generic swap copy — never block the page.
try {
const apiResponse = await fetch(
`${API_BASE}/swaps/${encodeURIComponent(swapId)}`,
);
if (apiResponse.ok) {
const { swap } = await apiResponse.json();
if (swap) {
const source = formatAmount(swap.sourceAmount, swap.sourceChain);
const dest = formatAmount(
swap.deliveredAmount ?? swap.destAmount,
swap.destChain,
);
let status = STATUS_LABELS[swap.status] || swap.status;
// "Completed in 27 seconds" — same speed flex as the card
const duration = formatDuration(
parseInt(swap.completedAt, 10) - parseInt(swap.initiatedAt, 10),
);
if (swap.status === "COMPLETED" && duration) {
status = `Completed in ${duration}`;
}
const seq = swap.seq != null ? `Transaction #${swap.seq}` : "Swap";
if (source && dest) {
// Plain "to", not "→": title text renders in the platform's own
// font, and U+2192 tofu-boxes on some of them. No brand suffix —
// og:site_name and the domain line already carry it.
title = `${source} to ${dest}`;
// Positive claim only (no "no bridges/IOUs" negations) — state
// what it is: the native asset, on the destination chain
description = `${seq} · ${status}. The native asset, settled on the destination chain. Delivery guaranteed on Allways, Bittensor Subnet 7.`;
imageAlt = `${seq} on Allways: ${source} to ${dest}, ${status.toLowerCase()}.`;
}
}
}
} catch (error) {
console.error("Failed to fetch swap for meta tags:", error);
}
}

// Note: home page and all other paths use the default brand card set above

const canonicalUrl = `${SITE_BASE}${pathname}`;

// Replace meta tags - use [\s\S]*? to match across newlines (the source
// index.html wraps long content attributes onto their own line).
return html
.replace(/<title>[\s\S]*?<\/title>/i, `<title>${escapeHtml(title)}</title>`)
.replace(
/<meta name="title"[\s\S]*?\/>/i,
`<meta name="title" content="${escapeHtml(title)}" />`,
)
.replace(
/<meta name="description"[\s\S]*?\/>/i,
`<meta name="description" content="${escapeHtml(description)}" />`,
)
.replace(
/<meta property="og:url"[\s\S]*?\/>/i,
`<meta property="og:url" content="${escapeHtml(canonicalUrl)}" />`,
)
.replace(
/<meta property="og:title"[\s\S]*?\/>/i,
`<meta property="og:title" content="${escapeHtml(title)}" />`,
)
.replace(
/<meta property="og:description"[\s\S]*?\/>/i,
`<meta property="og:description" content="${escapeHtml(description)}" />`,
)
.replace(
/<meta property="og:image"[\s\S]*?\/>/i,
`<meta property="og:image" content="${escapeHtml(image)}" />`,
)
.replace(
/<meta property="og:image:alt"[\s\S]*?\/>/i,
`<meta property="og:image:alt" content="${escapeHtml(imageAlt)}" />`,
)
.replace(
/<meta (property|name)="twitter:title"[\s\S]*?\/>/i,
`<meta name="twitter:title" content="${escapeHtml(title)}" />`,
)
.replace(
/<meta (property|name)="twitter:description"[\s\S]*?\/>/i,
`<meta name="twitter:description" content="${escapeHtml(description)}" />`,
)
.replace(
/<meta (property|name)="twitter:image"[\s\S]*?\/>/i,
`<meta name="twitter:image" content="${escapeHtml(image)}" />`,
)
.replace(
/<meta (property|name)="twitter:image:alt"[\s\S]*?\/>/i,
`<meta name="twitter:image:alt" content="${escapeHtml(imageAlt)}" />`,
);
}

/**
* Escape HTML special characters
*/
function escapeHtml(text) {
const map = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
return text.replace(/[&<>"']/g, (m) => map[m]);
}
8 changes: 4 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@
Facebook/X/Discord/Telegram don't execute JS, so the react-helmet tags
in SEO.tsx never reach them. Image URLs must be absolute for the same
reason. The SPA overwrites title/description per page after hydration. -->
<title>Allways — Any asset to any asset</title>
<meta name="title" content="Allways — Any asset to any asset" />
<title>Allways</title>
<meta name="title" content="Allways" />
<meta name="description"
content="Choose what you send, what arrives, and where it lands. Delivery guaranteed. Bittensor Subnet 7." />

<meta property="og:type" content="website" />
<meta property="og:site_name" content="Allways" />
<meta property="og:url" content="https://all-ways.io/" />
<meta property="og:title" content="Allways — Any asset to any asset" />
<meta property="og:title" content="Allways" />
<meta property="og:description"
content="Choose what you send, what arrives, and where it lands. Delivery guaranteed. Bittensor Subnet 7." />
<meta property="og:image" content="https://all-ways.io/og/allways-og.png" />
Expand All @@ -50,7 +50,7 @@
<meta property="og:image:alt" content="Allways — any asset to any asset. Universal transaction layer on Bittensor Subnet 7." />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Allways — Any asset to any asset" />
<meta name="twitter:title" content="Allways" />
<meta name="twitter:description"
content="Choose what you send, what arrives, and where it lands. Delivery guaranteed. Bittensor Subnet 7." />
<meta name="twitter:image" content="https://all-ways.io/og/allways-og.png" />
Expand Down
Binary file modified public/og/allways-og.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 11 additions & 42 deletions public/og/allways-og.template.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,35 @@
<html>
<head>
<meta charset="utf-8" />
<!--
Historical reference only: the canonical brand-card template now lives in
das-allways (src/og-images/templates/home.template.ts), and allways-og.png
is a render of GET /og-image. Keep this file visually in sync if edited.
-->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Inter:wght@400;900&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;900&display=swap" rel="stylesheet" />
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 1200px; height: 630px; overflow: hidden; }
.card {
position: relative;
width: 1200px;
height: 630px;
background:
radial-gradient(900px 540px at 88% 12%, rgba(0, 82, 255, 0.12), transparent 65%),
linear-gradient(120deg, #fbfbfb 55%, #eef3ff 100%);
background: linear-gradient(120deg, #fbfbfb 55%, #eef3ff 100%);
font-family: 'Inter', sans-serif;
color: #090b0d;
}
svg.circles { position: absolute; inset: 0; }
.inner { position: absolute; inset: 0; padding: 64px 72px; }
.toprow {
display: flex; justify-content: space-between; align-items: center;
}
.badge {
width: 84px; height: 84px; border-radius: 50%;
background: #ffffff; border: 1px solid rgba(9, 11, 13, 0.08);
box-shadow: 0 2px 18px rgba(9, 11, 13, 0.06);
display: flex; align-items: center; justify-content: center;
overflow: hidden;
}
.badge img { width: 86px; height: 86px; border-radius: 50%; }
.wordmark {
font-family: 'DM Mono', monospace; font-size: 24px; letter-spacing: 0.35em;
color: rgba(9, 11, 13, 0.55);
.inner {
position: absolute; inset: 0; padding: 0 72px;
display: flex; flex-direction: column; justify-content: center;
}
h1 {
margin-top: 64px;
font-size: 118px; font-weight: 900; line-height: 0.95;
font-size: 118px; font-weight: 900; line-height: 1.04;
letter-spacing: -0.03em;
}
h1 .blue { color: #0052ff; }
.sub {
margin-top: 40px;
font-size: 31px; font-weight: 400; color: rgba(9, 11, 13, 0.62);
letter-spacing: -0.01em;
}
.bottomrow {
position: absolute; left: 72px; right: 72px; bottom: 56px;
display: flex; justify-content: space-between; align-items: baseline;
font-family: 'DM Mono', monospace; font-size: 20px;
}
.eyebrow { color: #0052ff; letter-spacing: 0.22em; }
.tagline { color: rgba(9, 11, 13, 0.45); letter-spacing: 0.14em; }
</style>
</head>
<body>
Expand All @@ -63,16 +41,7 @@
<circle cx="1180" cy="500" r="230" stroke="#0052ff" stroke-opacity="0.16" stroke-width="1.5" />
</svg>
<div class="inner">
<div class="toprow">
<div class="badge"><img src="ETA_SRC" /></div>
<div class="wordmark">ALLWAYS</div>
</div>
<h1>Any asset<br /><span class="blue">to any asset.</span></h1>
<div class="sub">Choose what you send, what arrives, and where it lands.</div>
<div class="bottomrow">
<div class="eyebrow">BITTENSOR · SUBNET 7</div>
<div class="tagline">Universal transaction layer</div>
</div>
</div>
</div>
</body>
Expand Down
Loading