Skip to content
Open
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
7 changes: 7 additions & 0 deletions sdk/webpubsub-chat-client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
dist
types
tsconfig.tsbuildinfo
.env
.yarn
*.tgz
7 changes: 7 additions & 0 deletions sdk/webpubsub-chat-client/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
nodeLinker: node-modules

# Temporary: Use MyGet feed for @azure scoped packages during preview.
# This will be removed once the package is published to npm.
npmScopes:
azure:
npmRegistryServer: "https://www.myget.org/F/azure-signalr-dev/npm/"
121 changes: 121 additions & 0 deletions sdk/webpubsub-chat-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Azure Web PubSub Chat Client SDK

A client SDK for building chat applications with Azure Web PubSub.

> ⚠️ **Internal Preview**: This package is currently for internal use only and is not ready for production.

## Installation

```bash
npm install @azure/web-pubsub-chat-client
```

## Quick Start

For a complete example, see [examples/quickstart](./examples/quickstart).

```javascript
import { ChatClient } from '@azure/web-pubsub-chat-client';

// Get client access URL from your server
const url = await fetch('/negotiate?userId=alice').then(r => r.json()).then(d => d.url);

// Option 1: Login with an existing WebPubSubClient
const wpsClient = new WebPubSubClient(url);
const client = await ChatClient.login(wpsClient);

// Option 2: Login directly with URL
// const client = await new ChatClient(url).login();

console.log(`Logged in as: ${client.userId}`);

// Listen for events
client.addListenerForNewMessage((notification) => {
const msg = notification.message;
console.log(`${msg.createdBy}: ${msg.content.text}`);
});

client.addListenerForNewRoom((room) => {
console.log(`Joined room: ${room.title}`);
});

// Create a room and send messages
const room = await client.createRoom('My Room', ['bob']);
await client.sendToRoom(room.roomId, 'Hello!');

// Get message history
const history = await client.listRoomMessage(room.roomId, null, null);

// Manage room members
await client.addUserToRoom(room.roomId, 'charlie');
await client.removeUserFromRoom(room.roomId, 'charlie');

// Cleanup
client.stop();
```

## API

### ChatClient

#### Constructor

```typescript
// With existing WebPubSubClient
new ChatClient(wpsClient: WebPubSubClient)

// With client access URL
new ChatClient(clientAccessUrl: string, options?: WebPubSubClientOptions)

// With credential
new ChatClient(credential: WebPubSubClientCredential, options?: WebPubSubClientOptions)
```

#### Static Methods

| Method | Description |
|--------|-------------|
| `ChatClient.login(wpsClient)` | Create and login using an existing WebPubSubClient |

#### Properties

| Property | Type | Description |
|----------|------|-------------|
| `userId` | `string` | Current user's ID (throws if not logged in) |
| `rooms` | `RoomInfo[]` | List of joined rooms |
| `connection` | `WebPubSubClient` | Underlying WebPubSub connection |

#### Methods

| Method | Description |
|--------|-------------|
| `login()` | Connect and authenticate, returns `ChatClient` |
| `stop()` | Disconnect |
| `createRoom(title, members, roomId?)` | Create a new room with initial members |
| `getRoom(roomId, withMembers)` | Get room info |
| `addUserToRoom(roomId, userId)` | Add user to room (admin operation) |
| `removeUserFromRoom(roomId, userId)` | Remove user from room (admin operation) |
| `sendToRoom(roomId, message)` | Send text message to room, returns message ID |
| `listRoomMessage(roomId, startId, endId, maxCount?)` | Get room message history |
| `getUserInfo(userId)` | Get user profile |

#### Event Listeners

| Method | Callback Parameter | Description |
|--------|-------------------|-------------|
| `addListenerForNewMessage(callback)` | `NewMessageNotificationBody` | New message received |
| `addListenerForNewRoom(callback)` | `RoomInfo` | Joined a new room |
| `addListenerForMemberJoined(callback)` | `MemberJoinedNotificationBody` | Member joined a room |
| `addListenerForMemberLeft(callback)` | `MemberLeftNotificationBody` | Member left a room |
| `addListenerForRoomLeft(callback)` | `RoomLeftNotificationBody` | Self left a room |
| `onConnected(callback)` | `OnConnectedArgs` | Connection established |
| `onDisconnected(callback)` | `OnDisconnectedArgs` | Connection lost |
| `onStopped(callback)` | `OnStoppedArgs` | Connection stopped |

## Examples

See the [examples](./examples) directory for complete working examples.

## License

MIT
5 changes: 5 additions & 0 deletions sdk/webpubsub-chat-client/examples/quickstart/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
nodeLinker: node-modules

npmScopes:
azure:
npmRegistryServer: "https://www.myget.org/F/azure-signalr-dev/npm/"
44 changes: 44 additions & 0 deletions sdk/webpubsub-chat-client/examples/quickstart/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Minimal Example

A minimal example demonstrating the basic usage of Web PubSub Chat SDK.

## Prerequisites

1. An Azure Web PubSub resource with:
- A Persistent Storage configured (Storage Account with Table enabled)
- A Chat hub created (with Chat feature enabled, using the Persistent Storage above)

## Quick Start

```bash
yarn install
```

### 1. Start the server

```bash
yarn server -- "<your-webpubsub-connection-string>"
```

Or set the environment variable:

```bash
export WebPubSubConnectionString="<your-connection-string>"
yarn server
```

### 2. Run the client

In a new terminal:

```bash
yarn client
```

## What this example does

1. Creates two chat clients (Alice and Bob)
2. Alice creates a room and invites Bob
3. Alice sends messages to the room
4. Bob receives notifications for new room and messages
5. Lists message history from the room
100 changes: 100 additions & 0 deletions sdk/webpubsub-chat-client/examples/quickstart/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { ChatClient } from '@azure/web-pubsub-chat-client';
import { WebPubSubClient } from '@azure/web-pubsub-client';

const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3000';

const getClientAccessUrl = (userId) =>
fetch(`${SERVER_URL}/negotiate?userId=${userId}`).then(r => r.json()).then(d => d.url);

function setupListeners(client) {
// chat event listeners
client.addListenerForNewRoom((room) => {
console.log(`[${client.userId}] joined room "${room.title}" (${room.roomId})`);
});
client.addListenerForNewMessage((notification) => {
const msg = notification.message;
console.log(`[${client.userId}] received message from ${msg.createdBy}: ${msg.content.text}`);
});
client.addListenerForMemberJoined((info) => {
console.log(`[${client.userId}] saw ${info.userId} joined room ${info.roomId}`);
});
client.addListenerForMemberLeft((info) => {
console.log(`[${client.userId}] saw ${info.userId} left room ${info.roomId}`);
});
client.addListenerForRoomLeft((info) => {
console.log(`[${client.userId}] left room ${info.roomId}`);
});
// chat connection listeners
client.onStopped((e) => {
console.log(`connection used by ${client.userId} stopped`);
});
client.onDisconnected((e) => {
console.log(`connection used by ${client.userId} disconnected`);
});
}

async function main() {
// Create chat clients for Alice, Bob, and Mike

// Option 1: create a chat client with a existing WebPubSubClient
const url1 = await getClientAccessUrl('alice');
const webPubSubClient = new WebPubSubClient(url1);
const alice = await ChatClient.login(webPubSubClient);
console.log(`Alice logged in as: ${alice.userId}`);

// Option 2: create a chat client directly with client access URL
const url2 = await getClientAccessUrl('bob'), url3 = await getClientAccessUrl('mike');
const bob = await new ChatClient(url2).login();
const mike = await new ChatClient(url3).login();

console.log(`Bob logged in as: ${bob.userId}`);
console.log(`Mike logged in as: ${mike.userId}`);

// Setup event listeners

setupListeners(alice);
setupListeners(bob);
setupListeners(mike);

// Alice creates a room and invites Bob
console.log('\n--- Alice creates a room ---');
const room = await alice.createRoom('Hello World Room', [bob.userId]);

// Alice sends messages to the room
console.log('\n--- Alice sends messages ---');
for (let i = 1; i <= 3; i++) {
console.log(`[Alice] will send message #${i}`);
const msgId = await alice.sendToRoom(room.roomId, `Hello from Alice #${i}`);
}

// Bob replies to the room
console.log('\n--- Bob replies ---');
for (let i = 1; i <= 2; i++) {
console.log(`[Bob] will send message #${i}`);
const msgId = await bob.sendToRoom(room.roomId, `Hi Alice, this is Bob #${i}`);
}

// List message history
console.log('\n--- Message History ---');
const history = await alice.listRoomMessage(room.roomId, null, null);
for (const msg of history.messages) {
console.log(` [${msg.createdBy}] [${msg.createdAt}] ${msg.content.text}`);
}

// Alice manages room members
console.log('\n--- Alice manages room members ---');


// Alice adds mike to the room
await alice.addUserToRoom(room.roomId, mike.userId);

// Alice removes bob and mike from the room
await alice.removeUserFromRoom(room.roomId, bob.userId);
await alice.removeUserFromRoom(room.roomId, mike.userId);

// Cleanup
console.log('\n--- Cleanup ---');
[alice, bob, mike].forEach(client => client.stop());
}

main().catch(console.error);
17 changes: 17 additions & 0 deletions sdk/webpubsub-chat-client/examples/quickstart/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "quickstart-example",
"version": "1.0.0",
"description": "Quickstart example for Web PubSub Chat SDK",
"type": "module",
"scripts": {
"server": "node server.js",
"client": "node client.js"
},
"author": "Microsoft",
"license": "MIT",
"dependencies": {
"@azure/web-pubsub": "^1.2.0",
"@azure/web-pubsub-chat-client": "1.0.0-beta.1",
"express": "^5.2.1"
}
}
30 changes: 30 additions & 0 deletions sdk/webpubsub-chat-client/examples/quickstart/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import express from 'express';
import { WebPubSubServiceClient } from '@azure/web-pubsub';

const hubName = 'chat';
const port = 3000 || process.env.PORT

// Get connection string from environment variable or command line argument
const connectionString = process.env.WebPubSubConnectionString || process.argv[2];
if (!connectionString) {
console.error('Please provide WebPubSubConnectionString via environment variable or command line argument');
process.exit(1);
}

const app = express();
const serviceClient = new WebPubSubServiceClient(connectionString, hubName, { allowInsecureConnection: true });

// Negotiate endpoint for client to get access token
app.get('/negotiate', async (req, res) => {
console.log(`received negotiate request: ${JSON.stringify(req.query)}`);
const userId = req.query.userId;
if (!userId) {
return res.status(500).json({ error: 'userId is required' });
}
const token = await serviceClient.getClientAccessToken({ userId });
res.json({ url: token.url });
});

app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
Loading
Loading