Skip to content

Commit cdd9c52

Browse files
authored
[web-pubsub-client] Update readme (Azure#24291)
### Packages impacted by this PR web-pubsub-client ### Issues associated with this PR ### Describe the problem that is addressed by this PR Update Readme ### What are the possible designs available to address the problem? If there are more than one possible design, why was the one in this PR chosen? ### Are there test cases added in this PR? _(If not, why?)_ ### Provide a list of related PRs _(if any)_ ### Command used to generate this PR:**_(Applicable only to SDK release request PRs)_ ### Checklists - [ ] Added impacted package name to the issue description - [ ] Does this PR needs any fixes in the SDK Generator?** _(If so, create an Issue in the [Autorest/typescript](https://github.com/Azure/autorest.typescript) repository and link it here)_ - [ ] Added a changelog (if necessary)
1 parent ef769db commit cdd9c52

File tree

4 files changed

+166
-17
lines changed

4 files changed

+166
-17
lines changed

sdk/web-pubsub/web-pubsub-client/README.md

Lines changed: 160 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,102 @@ Details about the terms used here are described in [Key concepts](#key-concepts)
2929
npm install @azure/web-pubsub-client
3030
```
3131

32-
### 2. Create a `WebPubSubClient` and copy `client-access-url` from Azure Portal
32+
### 2. Authenticate the client
33+
34+
Client uses a Client Access URL to connect and authenticate with the service. The URL follow the patten as `wss://<service_name>.webpubsub.azure.com/client/hubs/<hub_name>?access_token=<token>`. The client has some different ways to get Client Access URL. As a quick start, you can copy and paste from Azure Portal, and for production, you usually need a negotiation server to generate the URL.
35+
36+
#### Use Client Access URL from Azure Portal
37+
38+
As a quick start, you can go to the Portal and copy the **Client Access URL** from **Key** blade.
39+
40+
![get_client_url](https://learn.microsoft.com/azure/azure-web-pubsub/media/howto-websocket-connect/generate-client-url.png)
41+
42+
As shown in the diagram, the client will be granted the permission of sending message to the specific group and joining the specific group. Learn more about client permission, see [permissions](https://learn.microsoft.com/azure/azure-web-pubsub/reference-json-reliable-webpubsub-subprotocol#permissions)
3343

3444
```js
3545
const { WebPubSubClient } = require("@azure/web-pubsub-client");
3646

37-
client = new WebPubSubClient("<<client-access-url>>");
47+
const client = new WebPubSubClient("<<client-access-url>>");
3848

3949
await client.start();
4050
```
4151

52+
#### Use negotiation server to generate Client Access URL
53+
54+
In production, client usually fetch Client Access URL from a negotiation server. The server holds the connection string and generates Client Access URL through `@azure/web-pubsub`.
55+
56+
The code snippet below is an example of negotiation server. The server exposes a `/negotiate` path and return the Client Access URL.
57+
58+
```js
59+
const express = require('express');
60+
const { WebPubSubServiceClient } = require('@azure/web-pubsub');
61+
62+
const app = express();
63+
const hubName = 'sample_chat';
64+
const port = 8080;
65+
66+
const serviceClient = new WebPubSubServiceClient("<<web-pubsub-connectionstring>>", "hubName");
67+
68+
app.get('/negotiate', async (req, res) => {
69+
let token = await serviceClient.getClientAccessToken({roles: ["webpubsub.joinLeaveGroup", "webpubsub.sendToGroup"] });
70+
res.json({
71+
url: token.url
72+
});
73+
});
74+
75+
app.use(express.static('dist'));
76+
app.listen(port, () => console.log(`Event handler listening at http://localhost:${port}/negotiate`));
77+
```
78+
79+
The code snippet below is the example of client side.
80+
81+
```js
82+
const { WebPubSubClient } = require("@azure/web-pubsub-client")
83+
84+
const client = new WebPubSubClient({
85+
getClientAccessUrl: async _ => {
86+
let value = await (await fetch(`/negotiate`)).json();
87+
return value.url;
88+
}
89+
});
90+
91+
await client.start();
92+
```
93+
94+
For the full code samples, please reach to [samples-browser](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/web-pubsub/web-pubsub-client/samples-browser)
95+
96+
### Join group and send message to group
97+
98+
The client can only receive messages from groups that it has joined and you need to add a callback to specify the logic when receiving messages.
99+
100+
```js
101+
client.on("group-message", (e) => {
102+
console.log(`Received message: ${e.message.data}`);
103+
});
104+
105+
let groupName = "group1";
106+
107+
// client need to join group to receive messages from the group.
108+
await client.joinGroup(groupName);
109+
110+
// send a message to group
111+
await client.sendToGroup(groupName, "hello world", "text");
112+
```
113+
42114
## Key concepts
43115

44116
### Connection
45117

46118
A connection, also known as a client or a client connection, represents an individual WebSocket connection connected to the Web PubSub service. When successfully connected, a unique connection ID is assigned to this connection by the Web PubSub service.
47119

120+
### Recovery
121+
122+
If using reliable protocols, a new WebSocket tries to establish using the connection ID of the lost connection. If the new WebSocket connection is successfully connected, the connection is recovered. And all group contexts will be recovered, and unreceived messages will be resent. If the service returns WebSocket error code `1008` or the recovery attemption lasts more than 30 seconds, the recovery fails.
123+
124+
### Reconnect
125+
126+
Reconnection happens when client connection drops and fails to recover. Reconnection just like a new connection which has a new connection ID. After reconnection, the group context or unreceived messages are lost. Client connection needs to rejoin groups. By default, client library rejoin group after reconnection.
127+
48128
### Hub
49129

50130
A hub is a logical concept for a set of client connections. Usually you use one hub for one purpose, for example, a chat hub, or a notification hub. When a client connection is created, it connects to a hub, and during its lifetime, it belongs to that hub. Different applications can share one Azure Web PubSub service by using different hub names.
@@ -57,24 +137,93 @@ A group is a subset of connections to the hub. You can add a client connection t
57137

58138
Connections to Web PubSub can belong to one user. A user might have multiple connections, for example when a single user is connected across multiple devices or multiple browser tabs.
59139

60-
### Client Events
140+
## Client Lifetime
61141

62-
Events are created during the lifecycle of a client connection. For example, a simple WebSocket client connection creates a `connect` event when it tries to connect to the service, a `connected` event when it successfully connected to the service, a `message` event when it sends messages to the service and a `disconnected` event when it disconnects from the service.
142+
Each of the Web PubSub client is safe to cache and use as a singleton for the lifetime of the application. The registered event callbacks share the same lifetime with the client. Which means you can add or remove callbacks at anytime and the registration status won't change after reconnection or even stopping the client.
63143

64-
### Event Handler
144+
## Examples
65145

66-
Event handler contains the logic to handle the client events. Event handler needs to be registered and configured in the service through the portal or Azure CLI beforehand. The place to host the event handler logic is generally considered as the server-side.
146+
### Specify subprotocol
67147

68-
## Examples
148+
You can change the subprotocol to be used in client. By default, the client uses `json.reliable.webpubsub.azure.v1`. In library, you can choose to use `json.reliable.webpubsub.azure.v1` or `json.webpubsub.azure.v1`.
69149

70-
### Start a client
150+
```js
151+
// Change to use json.webpubsub.azure.v1
152+
const client = new WebPubSubClient("<client-access-url>", { protocol: WebPubSubJsonProtocol() });
153+
```
71154

72155
```js
73-
const { WebPubSubClient } = require("@azure/web-pubsub-client");
156+
// Change to use json.reliable.webpubsub.azure.v1
157+
const client = new WebPubSubClient("<client-access-url>", { protocol: WebPubSubJsonReliableProtocol() });
158+
```
74159

75-
client = new WebPubSubClient("<<client-access-url>>");
160+
### Consume messages from server and from groups
76161

77-
await client.start();
162+
Client can add callbacks to consume messages from server and from groups. Please note, client can only receive group messages that it has joined.
163+
164+
```js
165+
client.on("server-message", (e) => {
166+
console.log(`Received message ${e.message.data}`);
167+
});
168+
169+
client.on("group-message", (e) => {
170+
console.log(`Received message from ${e.message.group}: ${e.message.data}`);
171+
});
172+
```
173+
174+
### Add callbacks for connected, disconnected and stopped events
175+
176+
When a client connection is connected to the service, the `connected` event is triggered once it received the connected message from the service.
177+
178+
```js
179+
client.on("connected", (e) => {
180+
console.log(`Connection ${e.connectionId} is connected.`);
181+
});
182+
```
183+
184+
When a client connection is disconnected and fails to recover, the disconnected event is triggered.
185+
186+
```js
187+
client.on("disconnected", (e) => {
188+
console.log(`Connection disconnected: ${e.message}`);
189+
});
190+
```
191+
192+
When a client is stopped, which means the client connection is disconnected and the client stops try to reconnect, the stopped event will be triggered. This usually happens after the `client.stop()` is called, or disabled `autoReconnect` or specify a limited reconnect retry count and the limit has reached. If you want to restart the client, you can call `client.start()` in the stopped event.
193+
194+
```js
195+
client.on("stopped", _ => {
196+
console.log(`Client has stopped`);
197+
});
198+
```
199+
200+
### Auto rejoin group and handle rejoin failure
201+
202+
When a client connection has dropped and fails to recover, all group context will be clean up in the service side. That means when the client reconnects, it needs to rejoin groups. By default, the client enabled `autoRejoinGroup` options. However, this feature has limitation. The client can only rejoin groups that it's originally joined by then client rather than joined by server side. And rejoin group operations may fail due to various reason, e.g. the client don't have the permission to join group. In such case, uses need to add a callback to handle the failure.
203+
204+
```js
205+
// By default autoRejoinGroups=true. You can disable it by setting to false.
206+
const client = new WebPubSubClient("<client-access-url>", { autoRejoinGroups: true });
207+
208+
client.on("rejoin-group-failed", e => {
209+
console.log(`Rejoin group ${e.group} failed: ${e.error}`);
210+
})
211+
```
212+
213+
### Operation and retry
214+
215+
By default, the operation like `client.joinGroup()`, `client.leaveGroup()`, `client.sendToGroup()`, `client.sendEvent()` has three reties. You can use `messageRetryOptions` to change. If all retries have failed, an error will be thrown. You can keep retry by pass in the same `ackId` as previous retries, thus the service can help to deduplicate the operation with the same `ackId`
216+
217+
```js
218+
try {
219+
await client.joinGroup(groupName);
220+
} catch (err) {
221+
let id = null;
222+
if (err instanceof SendMessageError) {
223+
id = err.ackId;
224+
}
225+
await client.joinGroup(groupName, {ackId: id});
226+
}
78227
```
79228

80229
## Troubleshooting

sdk/web-pubsub/web-pubsub-client/samples-dev/helloworld.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ async function main() {
5151
client.on("group-message", (e) => {
5252
if (e.message.data instanceof ArrayBuffer) {
5353
console.log(
54-
`Received message from ${groupName} ${Buffer.from(e.message.data).toString("base64")}`
54+
`Received message from ${e.message.group} ${Buffer.from(e.message.data).toString("base64")}`
5555
);
5656
} else {
57-
console.log(`Received message from ${groupName} ${e.message.data}`);
57+
console.log(`Received message from ${e.message.group} ${e.message.data}`);
5858
}
5959
});
6060

sdk/web-pubsub/web-pubsub-client/samples/v1-beta/javascript/helloworld.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ async function main() {
4646
client.on("group-message", (e) => {
4747
if (e.message.data instanceof ArrayBuffer) {
4848
console.log(
49-
`Received message from ${groupName} ${Buffer.from(e.message.data).toString("base64")}`
49+
`Received message from ${e.message.group} ${Buffer.from(e.message.data).toString("base64")}`
5050
);
5151
} else {
52-
console.log(`Received message from ${groupName} ${e.message.data}`);
52+
console.log(`Received message from ${e.message.group} ${e.message.data}`);
5353
}
5454
});
5555

sdk/web-pubsub/web-pubsub-client/samples/v1-beta/typescript/src/helloworld.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ async function main() {
5151
client.on("group-message", (e) => {
5252
if (e.message.data instanceof ArrayBuffer) {
5353
console.log(
54-
`Received message from ${groupName} ${Buffer.from(e.message.data).toString("base64")}`
54+
`Received message from ${e.message.group} ${Buffer.from(e.message.data).toString("base64")}`
5555
);
5656
} else {
57-
console.log(`Received message from ${groupName} ${e.message.data}`);
57+
console.log(`Received message from ${e.message.group} ${e.message.data}`);
5858
}
5959
});
6060

0 commit comments

Comments
 (0)