@mtgo-labs/wasm brings the mtgo Telegram MTProto client to the browser via WebAssembly. It exposes a small JavaScript API for creating a client, connecting over WebSocket, and invoking arbitrary Telegram TL methods — all running client-side in the browser.
flowchart LR
JS[Your JS code] -->|invoke method| W["@mtgo-labs/wasm .wasm"]
W -->|syscall/js| WS[Browser WebSocket]
WS -->|wss://| TG[Telegram DC]
TG --> WS --> W --> JS
npm install @mtgo-labs/wasm
# or
bun add @mtgo-labs/wasm
# or
pnpm add @mtgo-labs/wasmimport { load } from "@mtgo-labs/wasm";
import wasmUrl from "@mtgo-labs/wasm/mtgo-wasm.wasm?url";
import wasmExecUrl from "@mtgo-labs/wasm/wasm_exec.js?url";
const mtgo = await load({ wasmUrl, wasmExecUrl });
const client = mtgo.createClient({
apiID: 12345,
apiHash: "your_api_hash",
botToken: "123:ABCdefGHI", // or sessionString for a user session
});
await client.connect();
console.log("Logged in as", client.me());
// Invoke any TL method by name (snake_case params):
const result = await client.invoke("users.getUsers", {
id: [{ _: "inputUserSelf" }],
});
console.log(result);
await client.disconnect();SSR safety: call
load()insideonMount/useEffectonly — WASM has no server context.
If you're not using a bundler, use the browser subpath export. You can pull
everything from a CDN — no install needed:
<script src="https://unpkg.com/@mtgo-labs/wasm/wasm_exec.js"></script>
<script type="module">
import { load } from "https://unpkg.com/@mtgo-labs/wasm/browser";
const mtgo = await load("https://unpkg.com/@mtgo-labs/wasm/mtgo-wasm.wasm");
const client = mtgo.createClient({
apiID: 12345,
apiHash: "your_api_hash",
botToken: "123:ABCdefGHI",
});
await client.connect();
console.log(client.me());
</script>Or from node_modules after npm install:
<script src="node_modules/@mtgo-labs/wasm/wasm_exec.js"></script>
<script type="module">
import { load } from "node_modules/@mtgo-labs/wasm/browser";
const mtgo = await load("node_modules/@mtgo-labs/wasm/mtgo-wasm.wasm");
const client = mtgo.createClient({ apiID: 12345, apiHash: "...", botToken: "..." });
await client.connect();
</script>npm install @mtgo-labs/wasm<!-- src/routes/+page.svelte -->
<script>
import { onMount } from "svelte";
import { load } from "@mtgo-labs/wasm";
import wasmUrl from "@mtgo-labs/wasm/mtgo-wasm.wasm?url";
import wasmExecUrl from "@mtgo-labs/wasm/wasm_exec.js?url";
let client = null;
onMount(async () => {
// onMount only runs in the browser — SSR-safe.
const mtgo = await load({ wasmUrl, wasmExecUrl });
client = mtgo.createClient({
apiID: 12345,
apiHash: "your_hash",
botToken: "123:ABC",
});
await client.connect();
const me = await client.invoke("users.getUsers", { id: [{ _: "inputUserSelf" }] });
console.log(me);
});
</script>Instead of the wasmExecUrl import, you can load Go's bootstrap script
globally in app.html:
<!-- src/app.html — inside <head> -->
<script src="%sveltekit.assets%/wasm_exec.js"></script>Then copy wasm_exec.js into static/ and omit wasmExecUrl:
cp node_modules/@mtgo-labs/wasm/wasm_exec.js static/const mtgo = await load({ wasmUrl }); // wasmExecUrl not needed — Go is already global- SSR safety:
onMount/useEffectonly. Never callload()during SSR. Goglobal:wasm_exec.jssetsglobalThis.Go. Load it once, not per-component.?urlsuffix: Vite resolves@mtgo-labs/wasm/mtgo-wasm.wasm?urlto a served URL automatically — no need to copy files tostatic/.
| Option | Type | Required | Description |
|---|---|---|---|
apiID |
number |
yes* | Telegram API ID from my.telegram.org |
apiHash |
string |
yes* | Telegram API hash |
botToken |
string |
no | Bot token — triggers bot auth on connect |
sessionString |
string |
no | Pre-authenticated session (Telethon/Pyrogram/etc) |
phoneNumber |
string |
no | Phone number for interactive user login |
codeFunc |
function |
no | async (phone) => code — OTP provider |
passwordFunc |
function |
no | async (hint) => password — 2FA password |
* apiID/apiHash optional only when sessionString carries them.
Establishes the WebSocket transport, performs the DH key exchange, and authenticates (bot login or session restore). Returns a Promise.
Invokes a Telegram TL method by name. method is the TL function name
(e.g. "messages.sendMessage"). params is a plain JS object using
snake_case keys matching the TL schema. Returns the parsed response.
Returns the authenticated user ({ id, username, first_name, last_name, is_bot }),
or null if not connected.
Closes the transport and releases the session.
The client itself acts as a proxy — any property that isn't a known method
(connect, invoke, me, disconnect) is treated as a TL namespace,
giving you namespace.method(params) access to all Telegram TL methods:
const tg = MTGoWasm.createClient({ apiID: 12345, apiHash: "...", botToken: "..." });
await tg.connect();
// Set your username
await tg.account.updateUsername({ username: "new_name" });
// Update profile
await tg.account.updateProfile({
first_name: "John",
last_name: "Doe",
about: "Hello from WASM!",
});
// Send a message
await tg.messages.sendMessage({
peer: { _: "inputPeerSelf" },
message: "Hello from the browser!",
random_id: Date.now(),
});
// Get your own user info
const result = await tg.users.getUsers({
id: [{ _: "inputUserSelf" }],
});Params use snake_case keys matching the TL schema (same as invoke). Each call
returns a Promise resolving to the TL response.
| Subpath | Description |
|---|---|
@mtgo-labs/wasm |
Vite/bundler loader (default) |
@mtgo-labs/wasm/browser |
Plain-browser loader (no bundler) |
@mtgo-labs/wasm/wasm_exec.js |
Go's WASM bootstrap (globalThis.Go) |
@mtgo-labs/wasm/mtgo-wasm.wasm |
The compiled .wasm binary |
mtgo is a pure-Go MTProto client. This package adds two things:
-
A browser WebSocket transport —
wasm/wsconn.gowraps the browser's nativeWebSocketAPI as a Gonet.Conn. mtgo's obfuscated2 framing layer sits on top, exactly as it does for server-side WebSocket connections. -
A JS bridge —
wasm/bridge.gousessyscall/jsto exposecreateClient/connect/invoke/disconnectto JavaScript. RPC calls go through mtgo'sInvokeJSON, so every TL method is available without per-method glue code.
The mtgo side needs one hook: Config.WSDialer (landed in the mtgo release
after v0.12.0) lets this package inject the browser WebSocket as the transport
without reaching into mtgo internals.
- Traffic flows over
wss://(TLS) to Telegram's WebSocket endpoints (pluto.web.telegram.org/apiws, etc.). WebSocket connections are not bound by fetch CORS rules, so browsers can reach Telegram directly — GramJS and Telegram Web do the same. - All storage is in-memory (
InMemory: true). No filesystem is used.
If you need to rebuild the .wasm binary (e.g. after modifying the Go code):
git clone https://github.com/mtgo-labs/wasm.git
cd wasm
make build # produces mtgo-wasm.wasm
make copy-exec # copies Go's wasm_exec.js into lib/
make serve # starts a demo server at http://localhost:8080/Requires Go 1.22+ (for math/rand/v2) and the mtgo version that ships
Config.WSDialer + telegram.NewWSDialer.
MIT, same as mtgo.