A lightweight, embeddable widget that lets users pick from available platforms to receive an authentication code.
Self-hosted only. This widget is designed to be served from your own infrastructure. Clone this repository, serve the
widget/directory (along with the SVG assets) from your own domain or CDN, and reference your own URL in the examples below.
- The widget fetches your available platforms from your API.
- The user picks one.
- The widget calls your
onSelectcallback with the chosen platform key and closes itself. - You take it from there — send OTP and verify.
-
Clone this repository:
git clone https://github.com/shortmesh/Widgets.git
-
Serve the repo root (or
widget/and its sibling SVG assets) as static files from your own domain. -
Replace every
https://your-widget-host.complaceholder in the examples below with your actual URL.
Add one script tag — no build step, no dependencies.
<script src="https://your-widget-host.com/widget/widget.js"></script>Then open the widget from any button or event:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
<script src="https://your-widget-host.com/widget/widget.js"></script>
</head>
<body>
<button id="platform-btn">Choose platform</button>
<script>
document
.getElementById("platform-btn")
.addEventListener("click", function () {
ShortMeshWidget.open({
endpoints: {
platforms: "https://api.yoursite.com/platforms",
},
onSelect: function (platform) {
// platform is the key your API returned, e.g. "wa", "telegram", "signal"
console.log("User chose:", platform);
},
onError: function (error) {
console.error("Failed to load platforms:", error);
},
});
});
</script>
</body>
</html>Load the script once (e.g. in index.html or via useEffect), then call window.ShortMeshWidget.open().
Option A — script tag in index.html
<!-- public/index.html -->
<script src="https://your-widget-host.com/widget/widget.js"></script>Option B — load dynamically in a hook
// src/hooks/useShortMesh.js
import { useEffect } from "react";
export function useShortMesh() {
useEffect(() => {
if (document.getElementById("shortmesh-script")) return;
const script = document.createElement("script");
script.id = "shortmesh-script";
script.src = "https://your-widget-host.com/widget/widget.js";
document.body.appendChild(script);
}, []);
return function openWidget({ onSelect, onError } = {}) {
window.ShortMeshWidget?.open({
endpoints: {
platforms: "https://api.yoursite.com/platforms",
},
onSelect,
onError,
});
};
}Then use it in any component:
// src/components/PlatformPicker.jsx
import { useShortMesh } from "../hooks/useShortMesh";
export function PlatformPicker() {
const openWidget = useShortMesh();
function handleClick() {
openWidget({
onSelect: (platform) => {
console.log("User chose:", platform); // "wa" | "telegram" | "signal"
// kick off your OTP flow here
},
onError: (err) => {
console.error(err);
},
});
}
return <button onClick={handleClick}>Choose platform</button>;
}| Option | Type | Required | Description |
|---|---|---|---|
endpoints.platforms |
string | Yes | URL that returns your available platforms |
onSelect |
function | No | Called with the chosen platform key when the user confirms |
onError |
function | No | Called if the platforms request fails |
Your endpoint should return a JSON array:
[{ "platform": "wa" }]The widget maps these against its built-in registry and only shows platforms it recognises. Unknown platforms are silently skipped.
onSelect: (platform) => {
// platform is a string key, e.g. "wa", "telegram", "signal"
};The modal closes automatically after onSelect is called.
| Key | Platform |
|---|---|
wa |
|
signal |
Signal |
Authy is working to add more platforms.
The widget loads its platform icons (e.g. WhatsApp.svg, Telegram.svg, Signal.svg) relative to the URL of widget.js itself. During local development with Vite, document.currentScript.src resolves to localhost, so those asset requests hit your local dev server and may return 404 if the files are not being served there.
Fix — copy (or symlink) the SVG assets into your Vite project's public/ directory so Vite serves them alongside widget.js, or add proxy rules in vite.config.js pointing to your self-hosted instance:
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/widget/widget.js': {
target: 'https://your-widget-host.com',
changeOrigin: true,
secure: true,
},
'/Whatsapp.svg': {
target: 'https://your-widget-host.com',
changeOrigin: true,
secure: true,
},
'/Telegram.svg': {
target: 'https://your-widget-host.com',
changeOrigin: true,
secure: true,
},
'/Signal.svg': {
target: 'https://your-widget-host.com',
changeOrigin: true,
secure: true,
},
},
},
})Replace
https://your-widget-host.comwith the URL where you are serving your own instance of the widget.
See LICENSE for details.