WuKongIM Docs

Web quickstart

Install the pinned SDK version, obtain credentials from your backend, exchange online messages, and clean up.

This tutorial uses 2.0.5 to exchange online messages between Alice and Bob in separate clients. WebSocket JSON-RPC CONNECT authenticates the connection.

1. Prepare

Prepare the following:

  • A modern browser with native WebSocket, TextEncoder, and TextDecoder.
  • A WuKongIM single-node cluster or multi-node cluster whose /readyz is healthy and whose WebSocket Gateway is browser-reachable.
  • A same-origin product BFF (your application backend), or one with a deliberately configured cross-origin policy.
  • A BFF that validates the product session and returns uid, a short-lived token, and websocketUrl for the current user.

Read Identity & Token first. Production pages and WebSockets use HTTPS/WSS, and tokens never belong in URLs, Local Storage, logs, or analytics events.

To see messages first, follow the official example with two clients. The steps below integrate the SDK into your application.

The default device category is WEB 1. Store the Token with the same device_flag on your backend: APP is 0, WEB is 1, and PC is 2.

2. Install the SDK

Run this in an existing Vite, Next.js, or other bundled application:

npm install --save-exact easyjssdk@2.0.5

Commit package-lock.json. This tutorial does not use a floating CDN version that could silently replace the SDK after a page refresh.

3. Connect and listen

/api/im/bootstrap below is your product route, not WuKongIM Product HTTP. The BFF validates the server-side login session, issues a short-lived token, and safely selects the WebSocket address returned by /route:

interface IMBootstrap {
  uid: string;
  token: string;
  websocketUrl: string;
}

async function fetchIMBootstrap(): Promise<IMBootstrap> {
  const response = await fetch('/api/im/bootstrap', {
    credentials: 'include',
    cache: 'no-store',
    headers: { accept: 'application/json' },
  });
  if (!response.ok) throw new Error(`IM bootstrap failed: ${response.status}`);
  return response.json() as Promise<IMBootstrap>;
}

The browser never calls /user/token or /route directly. Their management boundary, server credential, and response filtering remain inside the BFF.

on receives a function reference; off must receive the same reference. This example disables global-singleton mode so hot reload, tests, or a multi-instance page cannot silently destroy another client:

import { WKIM, WKIMChannelType, WKIMEvent, type RecvMessage } from 'easyjssdk';

export class EasyChatClient {
  private im?: WKIM;
  constructor(private readonly receiveMessage: (message: RecvMessage) => void) {}

  private readonly onConnect = (_result: unknown) => {
    console.info('EasySDK connected');
  };

  private readonly onDisconnect = (_info: unknown) => {
    console.info('EasySDK disconnected');
  };

  private readonly onMessage = (message: RecvMessage) => {
    this.receiveMessage(message);
  };

  private readonly onError = (_error: unknown) => {
    console.error('EasySDK operation failed');
  };

  async start(bootstrap: IMBootstrap): Promise<void> {
    this.stop();

    const im = WKIM.init(
      bootstrap.websocketUrl,
      { uid: bootstrap.uid, token: bootstrap.token },
      { singleton: false, debugLogging: false },
    );
    im.on(WKIMEvent.Connect, this.onConnect);
    im.on(WKIMEvent.Disconnect, this.onDisconnect);
    im.on(WKIMEvent.Message, this.onMessage);
    im.on(WKIMEvent.Error, this.onError);

    this.im = im;
    await this.connectWithin(im, 10_000);
  }

  private async connectWithin(im: WKIM, timeoutMs: number): Promise<void> {
    let timeout: ReturnType<typeof setTimeout> | undefined;
    try {
      await Promise.race([
        im.connect(),
        new Promise<void>((_, reject) => {
          timeout = setTimeout(
            () => reject(new Error(`EasySDK connect exceeded ${timeoutMs} ms`)),
            timeoutMs,
          );
        }),
      ]);
    } catch (error) {
      im.destroy();
      if (this.im === im) this.im = undefined;
      throw error;
    } finally {
      if (timeout) clearTimeout(timeout);
    }
  }

  async sendText(targetUid: string, text: string) {
    if (!this.im?.isConnected) throw new Error('EasySDK is not connected');

    return this.im.send(targetUid, WKIMChannelType.Person, {
      type: 1,
      version: 1,
      content: text,
    });
  }

  stop(): void {
    const im = this.im;
    if (!im) return;

    im.off(WKIMEvent.Connect, this.onConnect);
    im.off(WKIMEvent.Disconnect, this.onDisconnect);
    im.off(WKIMEvent.Message, this.onMessage);
    im.off(WKIMEvent.Error, this.onError);
    im.destroy();
    this.im = undefined;
  }
}

debugLogging: false matches the SDK default and is written explicitly here so production configuration can be reviewed. Set it to true only during a controlled diagnostic window; the SDK still emits only sanitized operational metadata, and application callbacks or collectors must not log complete events, results, or payloads.

An application normally creates one EasyChatClient. The SDK has an internal timeout for the CONNECT JSON-RPC request, but the WebSocket-open phase can still hang; connectWithin bounds the complete wait at 10 seconds and calls destroy after failure. React can keep the client in a root-level Provider and call stop() from an Effect cleanup. Vue and Svelte use the corresponding component or application unmount hook.

4. Exchange the first message

Add a button and receive area to the page. Click send after both clients connect; on Bob’s page change the target from bob to alice. The UI displays only the latest message, without retaining an unbounded history.

<button id="send-message" disabled>Send</button>
<pre id="received-message"></pre>
const output = document.querySelector<HTMLElement>('#received-message');
const button = document.querySelector<HTMLButtonElement>('#send-message');
if (!output || !button) throw new Error('Missing message UI');
button.disabled = true;
const chat = new EasyChatClient((message) => {
  output.textContent = JSON.stringify({
    fromUid: message.fromUid,
    payload: message.payload,
  });
});
window.addEventListener('pagehide', () => chat.stop(), { once: true });
try {
  await chat.start(await fetchIMBootstrap());
  button.disabled = false;
} catch {
  chat.stop();
  output.textContent = 'Connection failed';
}
button.onclick = async () => {
  try {
    // Bob sends to 'alice'. Click only after both clients are connected.
    await chat.sendText('bob', 'Hello from Web EasySDK');
    console.info('SEND completed');
  } catch {
    console.error('Send failed; check its outcome before retrying');
  }
};

Alice's resolved Promise means the send request received a server result. Bob must still observe the message in an independent page through WKIMEvent.Message. Do not render Alice's send result as proof that Bob read or processed it.

  1. Open two independent browser profiles or isolated browser contexts and sign in as Alice and Bob.
  2. Both sides obtain connection material from their own product session and observe WKIMEvent.Connect.
  3. Have Alice send to the person Channel bob, retaining the send result.
  4. On Bob, verify fromUid, channelId, messageId, and payload.
  5. Have Bob send back to Alice and prove the reverse direction.
  6. Use an unreachable address and confirm failure plus destroy within 10 seconds, rather than leaving the page in Connecting forever.
  7. Refresh, close, or sign out and confirm that the old instance called off and destroy, leaving no duplicate connection.

5. Clean up

Call chat.stop() on logout, application unmount, or page exit. It removes callbacks with the same function references and calls destroy(). Page subscribers remove their handlers; the connection owner destroys the instance.

6. Troubleshooting

  • The page says WebSocket or encoding APIs are missing: confirm the browser and build environment provide WebSocket, TextEncoder, and TextDecoder; do not infer Mini Program support from the browser path.
  • Connection is rejected before ready: inspect the BFF-returned WSS address, token lifetime, certificate, proxy Upgrade, and Gateway reachability.
  • Messages duplicate after refresh: do not call on during every component render; retain callbacks and call off/destroy during unmount.
  • The console still contains a token or payload: first use the lockfile and browser bundle to confirm that easyjssdk@2.0.5 is actually shipped, then inspect application event callbacks, error handling, and collectors for complete-object logging. SDK diagnostics are off by default and should emit only sanitized operational metadata when explicitly enabled. Reproduce with the npm artifact and retain only low-sensitivity evidence before filing an issue.
  • You need an executable Web scenario path: use the WuKongIMJSSDK Web tutorial, which has the site's protected verification flow, and review the current build and runtime boundaries under API Versions & Compatibility. Do not mix method names between the two SDKs.

Next

Continue with messaging and production checks. For offline recovery, conversations, unread counts, or push, see SDK selection. Versions and validation records retain the exact environments and scope of past runs.

On this page