npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mtgo-labs/wasm

v0.4.0

Published

Telegram MTProto client for the browser via WebAssembly — powered by mtgo.

Readme

@mtgo-labs/wasm

Go mtgo license npm

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

Install

npm install @mtgo-labs/wasm
# or
bun add @mtgo-labs/wasm
# or
pnpm add @mtgo-labs/wasm

Quick start (Vite / SvelteKit / Next.js)

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";

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() inside onMount / useEffect only — WASM has no server context.

Plain HTML (no bundler)

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>

SvelteKit integration

1. Install

npm install @mtgo-labs/wasm

2. Use in a component

<!-- 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>

Alternative: load wasm_exec.js via app.html

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

Key points

  • SSR safety: onMount / useEffect only. Never call load() during SSR.
  • Go global: wasm_exec.js sets globalThis.Go. Load it once, not per-component.
  • ?url suffix: Vite resolves @mtgo-labs/wasm/mtgo-wasm.wasm?url to a served URL automatically — no need to copy files to static/.

API

MTGoWasm.createClient(opts) → client

| 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.

client.connect() → Promise<void>

Establishes the WebSocket transport, performs the DH key exchange, and authenticates (bot login or session restore). Returns a Promise.

client.invoke(method, params) → Promise<object>

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.

client.me() → object | null

Returns the authenticated user ({ id, username, first_name, last_name, is_bot }), or null if not connected.

client.disconnect() → Promise<void>

Closes the transport and releases the session.

TL namespace proxy

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.

Package exports

| 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 |

How it works

mtgo is a pure-Go MTProto client. This package adds two things:

  1. A browser WebSocket transportwasm/wsconn.go wraps the browser's native WebSocket API as a Go net.Conn. mtgo's obfuscated2 framing layer sits on top, exactly as it does for server-side WebSocket connections.

  2. A JS bridgewasm/bridge.go uses syscall/js to expose createClient/connect/invoke/disconnect to JavaScript. RPC calls go through mtgo's InvokeJSON, so every TL method is available without per-method glue code.

The mtgo side needs one hook: Config.WSDialer (landed in mtgo v0.12.1) lets this package inject the browser WebSocket as the transport without reaching into mtgo internals.

Transport notes

  • 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.

Cloudflare Workers / Pages

Cloudflare enforces a 25 MiB per-file limit. Ship the pre-compressed .wasm.gz (5.2 MB) instead of the raw 29.5 MB binary — the JS loaders decompress it transparently in the browser:

import { load } from "@mtgo-labs/wasm";
import wasmUrl from "@mtgo-labs/wasm/mtgo-wasm.wasm.gz?url";

const mtgo = await load({ wasmUrl });

For plain HTML/CDN usage:

const mtgo = await load("https://unpkg.com/@mtgo-labs/wasm/mtgo-wasm.wasm.gz");

The loaders detect gzip by magic bytes (0x1f 0x8b) and decompress via DecompressionStream, so no server-side Content-Encoding: gzip header is needed.

Building from source

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.26+ and the mtgo version that ships Config.WSDialer + telegram.NewWSDialer.

License

MIT, same as mtgo.