docs: update realtime websockets guide (#19660)

* docs: update realtime websockets guide

* update: import from unpkg

* Update docs/guides/real-time/getting-started/websockets.md

Co-authored-by: Kevin Lewis <kvn@lws.io>

* update docs

* add note

* update connection pong

* update getting started guide

* update js chat guide

* update query

* update sdk query

* update js docs

* update react docs

* make update to js chat docs

* add history of previous msgs to js docs

* tweak sentence

* update react chat docs

* update live poll docs

* format react docs

* fix typo

* fix format

* update vue docs

* Update docs/guides/real-time/chat/javascript.md

Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>

* fix indentation

* remove console.log

* Update docs/guides/real-time/chat/javascript.md

Co-authored-by: Kevin Lewis <kvn@lws.io>

* Update docs/guides/real-time/chat/javascript.md

Co-authored-by: Kevin Lewis <kvn@lws.io>

* Update docs/guides/real-time/chat/javascript.md

Co-authored-by: Kevin Lewis <kvn@lws.io>

* removed strict equality

* Update docs/guides/real-time/chat/javascript.md

Co-authored-by: Kevin Lewis <kvn@lws.io>

* add the JS ;

* Update docs/guides/real-time/chat/react.md

Co-authored-by: Kevin Lewis <kvn@lws.io>

* removed unnecessary console.log

* update method definitions

* add link to websockets without sdk

* Update docs/guides/real-time/getting-started/websockets.md

Co-authored-by: Kevin Lewis <kvn@lws.io>

* Update docs/guides/real-time/getting-started/websockets.md

Co-authored-by: Kevin Lewis <kvn@lws.io>

* tiny text updates

---------

Co-authored-by: Kevin Lewis <kvn@lws.io>
Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>
This commit is contained in:
Esther Agbaje
2024-01-26 09:12:08 +00:00
committed by GitHub
parent ce0a9b34a4
commit 64fd6701d8
6 changed files with 1237 additions and 899 deletions

View File

@@ -7,7 +7,7 @@ description: Learn how to build a real-time multi-user chat with WebSockets and
In this guide, you will build a multi-user chat application with Directus WebSockets interface that authenticate users
with an existing account, show historical messages stored in Directus, allow users to send new messages, and immediately
update all connected chats.
updates all connected chats.
## Before You Start
@@ -17,7 +17,7 @@ You will need a Directus project. If you dont already have one, the easiest w
[managed Directus Cloud service](https://directus.cloud).
Create a new collection called `messages`, with `date_created` and `user_created` fields enabled in the _Optional System
Fields_ pane on collection creation. Create a text field called `text`.
Fields_ pane on collection creation. Create an input field called `text`.
Create a new Role called `Users`, and give Create and Read access to the `Messages` collection, and Read access to the
`Directus Users` system collection. Create a new user with this role. Make note of the password you set.
@@ -35,7 +35,7 @@ Create an `index.html` file and open it in your code editor:
<input type="email" id="email" />
<label for="password">Password</label>
<input type="password" id="password" />
<input type="submit" />
<button type="submit">Submit</button>
</form>
<ol></ol>
@@ -43,7 +43,7 @@ Create an `index.html` file and open it in your code editor:
<form id="new">
<label for="message">Message</label>
<input type="text" id="text" />
<input type="submit" />
<button type="submit">Submit</button>
</form>
<script></script>
@@ -57,13 +57,42 @@ populated with messages.
Inside of the `<script>`, create a `url` variable being sure to replace `your-directus-url` with your projects URL:
```js
const url = 'wss://your-directus-url/websocket';
let connection;
const url = 'https://your-directus-url';
```
The `connection` variable will later contain a WebSocket instance.
## Import the SDK Composables
Finally, create event listeners which are triggered on the form submissions:
At the top of the `<script>` tag, import the SDK composables needed for this project
```html
<!doctype html>
<html>
<body>
<script>
import { createDirectus, authentication, realtime } from 'https://www.unpkg.com/@directus/sdk/dist/index.js'; // [!code ++]
const url = 'https://your-directus-url';
</script>
</body>
</html>
```
- `createDirectus` is a function that initializes a Directus client.
- `authentication` provides methods to authenticate a user.
- `realtime` provides methods to establish a WebSocket connection.
## Establish and Authenticate a WebSocket Client
Create and authenticate the WebSocket client
```js
const client = createDirectus(url)
.with(authentication())
.with(realtime());
```
Now, extract the `email` and `password` values from the form. To do this, create event listeners which are triggered on
the form submissions:
```js
document.querySelector('#login').addEventListener('submit', function (event) {
@@ -75,74 +104,64 @@ document.querySelector('#new').addEventListener('submit', function (event) {
});
```
## Establish WebSocket Connection
Within the `#login` form submit event handler, extract the `email` and `password` values from the form:
Within the `#login` form submit event handler, get access to the email and password values
```js
const email = event.target.elements.email.value;
const password = event.target.elements.password.value;
```
Create a new WebSocket, which will immediately attempt connection:
Now, call the login method on the client, passing the email and password
```js
connection = new WebSocket(url);
document.querySelector('#login').addEventListener('submit', function (event) {
event.preventDefault();
const email = event.target.elements.email.value;
const password = event.target.elements.password.value;
client.login(email, password); // [!code ++]
});
```
On connection, you must [send an authentication message before the timeout](/guides/real-time/authentication). Add an
event handler for the connection's `open` event:
Once the client is authenticated, immediately create a WebSocket connection:
```js
connection.addEventListener('open', function () {
connection.send(
JSON.stringify({
type: 'auth',
email,
password,
})
);
});
client.connect();
```
## Subscribe To Messages
In a WebSocket connection, all data sent from the server will trigger the connections `message` event. Underneath the
`open` event handler, add the following:
As soon as you have successfully authenticated, a message will be sent. When this happens, subscribe to updates on the
`Messages` collection.
```js
connection.addEventListener('message', function (message) {
receiveMessage(message);
client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
subscribe('update')
}
});
```
At the bottom of your `<script>`, create the `receiveMessage` function:
Create a `subscribe` function that subscribes to events.
```js
function receiveMessage(message) {
const data = JSON.parse(message.data);
async function subscribe(event) {
const { subscription } = await client.subscribe('messages', {
event,
query: {
fields: ['*', 'user_created.first_name'],
},
});
for await (const message of subscription) {
receiveMessage(message);
}
}
```
As soon as you have successfully authenticated, a message will be sent. When this happens, subscribe to updates on the
`Messages` collection. Add this inside of the `receiveMessage` function:
```js
if (data.type == 'auth' && data.status == 'ok') {
connection.send(
JSON.stringify({
type: 'subscribe',
collection: 'messages',
query: {
fields: ['*', 'user_created.first_name'],
sort: 'date_created',
},
})
);
}
```
When a subscription is started, a message will be sent to confirm. Add this inside of the `receiveMessage` function:
When a subscription is started, a message will be sent to confirm. Create a `receiveMessage` function with the
following:
```js
if (data.type == 'subscription' && data.event == 'init') {
@@ -162,14 +181,12 @@ document.querySelector('#new').addEventListener('submit', function (event) {
event.preventDefault();
const text = event.target.elements.text.value; // [!code ++]
connection.send( // [!code ++]
JSON.stringify({ // [!code ++]
type: 'items', // [!code ++]
collection: 'messages', // [!code ++]
action: 'create', // [!code ++]
data: { text }, // [!code ++]
}) // [!code ++]
); // [!code ++]
client.sendMessage({ // [!code ++]
type: 'items', // [!code ++]
collection: 'messages', // [!code ++]
action: 'create', // [!code ++]
data: { text }, // [!code ++]
});
document.querySelector('#text').value = ''; // [!code ++]
});
@@ -208,18 +225,51 @@ and navigate to your index.html file, login and submit a message there and both
## Display Historical Messages
Replace the `console.log()` you created when the subscription is initialized:
To display the list of all existing messages, create a function `readAllMessages` with the following:
```js
if (data.type == 'subscription' && data.event == 'init') {
console.log('subscription started'); // [!code --]
for (const message of data.data) { // [!code ++]
addMessageToList(message); // [!code ++]
} // [!code ++]
function readAllMessages() {
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'read',
query: {
limit: 10,
sort: '-date_created',
fields: ['*', 'user_created.first_name'],
},
});
}
```
Invoke this function directly before subscribing to any events
```js
client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
readAllMessages(); // [!code ++]
subscribe('create');
}
});
```
Within the connection, listen for "items" message to update the user interface with message history.
```js
client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
readAllMessages();
subscribe('create');
}
if (data.type == 'items') { // [!code ++]
for (const item of data.data) { // [!code ++]
addMessageToList(item); // [!code ++]
} // [!code ++]
} // [!code ++]
});
```
Refresh your browser, login, and you should see the existing messages shown in your browser.
## Next Steps
@@ -236,91 +286,105 @@ This guide covers authentication, item creation, and subscription using WebSocke
```html
<!DOCTYPE html>
<html>
<body>
<form id="login">
<label for="email">Email</label>
<input type="email" id="email" />
<label for="password">Password</label>
<input type="password" id="password" />
<input type="submit" />
</form>
<body>
<form id="login">
<label for="email">Email</label>
<input type="email" id="email" />
<label for="password">Password</label>
<input type="password" id="password" />
<input type="submit" />
</form>
<ol></ol>
<ol></ol>
<form id="new">
<label for="message">Message</label>
<input type="text" id="text" />
<input type="submit" />
</form>
<form id="new">
<label for="message">Message</label>
<input type="text" id="text" />
<input type="submit" />
</form>
<script>
const url = 'wss://your-directus-url/websocket';
let connection;
<script>
import {
createDirectus,
authentication,
realtime,
} from 'https://www.unpkg.com/@directus/sdk/dist/index.js';
document.querySelector('#login').addEventListener('submit', function (event) {
event.preventDefault();
const email = event.target.elements.email.value;
const password = event.target.elements.password.value;
connection = new WebSocket(url);
connection.addEventListener('open', function () {
connection.send(
JSON.stringify({
type: 'auth',
email,
password,
})
);
});
connection.addEventListener('message', function (message) {
receiveMessage(message);
});
});
const url = 'https://your-directus-url';
document.querySelector('#new').addEventListener('submit', function (event) {
event.preventDefault();
const text = event.target.elements.text.value;
connection.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'create',
data: { text },
})
);
document.querySelector('#text').value = '';
});
const client = createDirectus(url)
.with(authentication())
.with(realtime());
function receiveMessage(message) {
const data = JSON.parse(message.data);
if (data.type == 'auth' && data.status == 'ok') {
connection.send(
JSON.stringify({
type: 'subscribe',
collection: 'messages',
query: {
fields: ['*', 'user_created.first_name'],
sort: 'date_created',
},
})
);
}
if (data.type == 'subscription' && data.event == 'init') {
for (const message of data.data) {
addMessageToList(message);
}
}
if (data.type == 'subscription' && data.event == 'create') {
addMessageToList(data.data[0]);
}
}
client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
readAllMessages();
subscribe('create');
}
function addMessageToList(message) {
const li = document.createElement('li');
li.setAttribute('id', message.id);
li.textContent = `${message.user_created.first_name}: ${message.text}`;
document.querySelector('ol').appendChild(li);
}
</script>
</body>
if (data.type == 'items') {
for (const item of data.data) {
addMessageToList(item);
}
}
});
client.connect();
document
.querySelector('#login')
.addEventListener('submit', function (event) {
event.preventDefault();
const email = event.target.elements.email.value;
const password = event.target.elements.password.value;
client.login(email, password);
});
document
.querySelector('#new')
.addEventListener('submit', function (event) {
event.preventDefault();
const text = event.target.elements.text.value;
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'create',
data: { text },
});
});
async function subscribe(event) {
const { subscription } = await client.subscribe('messages', {
event,
query: {
fields: ['*', 'user_created.first_name'],
},
});
for await (const message of subscription) {
receiveMessage(message);
}
}
function receiveMessage(data) {
if (data.type == 'subscription' && data.event == 'init') {
console.log('subscription started');
}
if (data.type == 'subscription' && data.event == 'create') {
addMessageToList(message.data[0]);
}
}
function addMessageToList(message) {
const li = document.createElement('li');
li.setAttribute('id', message.id);
li.textContent = `${message.user_created.first_name}: ${message.text}`;
document.querySelector('ol').appendChild(li);
}
</script>
</body>
</html>
```

View File

@@ -17,7 +17,7 @@ You will need a Directus project. If you dont already have one, the easiest w
[managed Directus Cloud service](https://directus.cloud).
Create a new collection called `messages`, with `date_created` and `user_created` fields enabled in the _Optional System
Fields_ pane on collection creation. Create a text field called `text`.
Fields_ pane on collection creation. Create an input field called `text`.
Create a new Role called `Users`. Give Create and Read access to the `Messages` collection, and Read access to the
`Directus Users` system collection. Now, create a new user with this role and take note of the password you set.
@@ -40,7 +40,7 @@ function App() {
<form>
<label htmlFor="message">Message</label>
<input type="text" id="message" />
<input type="text" id="text" />
<button type="submit">Submit</button>
</form>
</div>
@@ -54,14 +54,35 @@ populated with messages we will create shortly.
Create a `url` variable and be sure to replace `your-directus-url` with your projects URL:
```js
const url = 'wss://your-directus-url/websocket';
const url = 'https://your-directus-url';
```
Now, create a variable called `connectionRef` that has an initial null value. The `connectionRef` will later contain a
WebSocket instance.
## Import the Required Composables and Methods
At the top of your file, import the SDK composables needed for this project
```js
const connectionRef = useRef(null);
import { authentication, createDirectus, realtime } from '@directus/sdk';
```
- `createDirectus` is a function that initializes a Directus client.
- `authentication` provides methods to authenticate a user.
- `realtime` provides methods to establish a WebSocket connection.
Also import `useState` and `useEffect` from react.
```js
import { useState, useEffect } from 'react';
```
## Establish and Authenticate a WebSocket Client
Create and authenticate the WebSocket client
```js
const client = createDirectus(url)
.with(authentication())
.with(realtime());
```
## Set Up Form Submission Methods
@@ -87,129 +108,67 @@ const messageSubmit = (event) => {
};
```
## Establish WebSocket Connection
At the top of your component, create a piece of state to hold the `email` and `password` values of the login form:
Now, extract the `email` and `password` values from the login form.
```js
const [formValue, setFormValue] = useState({ email: '', password: '' });
const loginSubmit = (event) => {
event.preventDefault();
const email = event.target.elements.email.value; // [!code ++]
const password = event.target.elements.password.value; // [!code ++]
};
```
Set up a `handleLoginChange` method that updates the value of the login input field as the user types.
Once the client is authenticated, immediately create a WebSocket connection:
```js
const handleLoginChange = (event) => {
setFormValue({ ...formValue, [event.target.name]: event.target.value });
};
client.connect();
```
Then, connect these values to the form input fields:
## Subscribe To Messages
```jsx
<form onSubmit={loginSubmit}>
<label htmlFor="email">Email</label>
<input type="email" id="email" /> // [!code --]
<input type="email" id="email" name="email" value={formValue.email} onChange={handleLoginChange} /> // [!code ++]
<label htmlFor="password">Password</label>
<input type="password" id="password" /> // [!code --]
<input type="password" id="password" name="password" value={formValue.password} onChange={handleLoginChange} /> // [!code ++]
<button type="submit">Submit</button>
</form>
```
Within the `loginSubmit` method, create a new WebSocket, which will immediately attempt connection:
As soon as you have successfully authenticated, a message will be sent. When this happens, within `useEffect`, subscribe
to updates on the `Messages` collection.
```js
const loginSubmit = (event) => {
connectionRef.current = new WebSocket(url); // [!code ++]
};
useEffect(() => {
const cleanup = client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
subscribe('create');
}
});
client.connect();
return cleanup;
}, []);
```
On connection, you must [send an authentication message before the timeout](/guides/real-time/authentication). Add an
event handler for the connection's `open` event:
Create a `subscribe` function that subscribes to events.
```js
const loginSubmit = (event) => {
connectionRef.current = new WebSocket(url);
connectionRef.current.addEventListener('open', authenticate(formValue)); // [!code ++]
};
async function subscribe(event) {
const { subscription } = await client.subscribe('messages', {
event,
query: {
fields: ['*', 'user_created.first_name'],
},
});
for await (const message of subscription) {
receiveMessage(message);
}
}
```
Then, create a new `authenticate` method:
When a subscription is started, a message will be sent to confirm. Create a `receiveMessage` function with the
following:
```js
const authenticate = (opts) => {
const { email, password } = opts;
connectionRef.current.send(JSON.stringify({ type: 'auth', email, password }));
};
```
### Subscribe to Messages
In a WebSocket connection, all data sent from the server will trigger the connections `message` event. Inside
`loginSubmit`, add an event handler:
```js
const loginSubmit = (event) => {
connectionRef.current = new WebSocket(url);
connectionRef.current.addEventListener('open', authenticate(formValue));
connectionRef.current.addEventListener('message', (message) => receiveMessage(message)); // [!code ++]
};
```
Then, create a new `receiveMessage` method:
```js
const receiveMessage = (message) => {
const data = JSON.parse(message.data);
};
```
As soon as you have successfully authenticated, a message will be sent. When this happens, subscribe to updates on the
`Messages` collection. Add this inside of the `receiveMessage` method:
```js
const receiveMessage = (message) => {
const data = JSON.parse(message.data);
if (data.type === 'auth' && data.status === 'ok') { // [!code ++]
connectionRef.current.send( // [!code ++]
JSON.stringify({ // [!code ++]
type: 'subscribe', // [!code ++]
collection: 'messages', // [!code ++]
query: { // [!code ++]
fields: ['*', 'user_created.first_name'], // [!code ++]
sort: 'date_created', // [!code ++]
}, // [!code ++]
}) // [!code ++]
); // [!code ++]
} // [!code ++]
};
```
When a subscription is started, a message will be sent to confirm. Add this inside of the `receiveMessage` method:
```js {15-17}
const receiveMessage = (message) => {
const data = JSON.parse(message.data);
if (data.type === 'auth' && data.status === 'ok') {
connectionRef.current.send(
JSON.stringify({
type: 'subscribe',
collection: 'messages',
query: {
fields: ['*', 'user_created.first_name'],
sort: 'date_created',
},
})
);
}
if (data.type === 'subscription' && data.event === 'init') { // [!code ++]
console.log('subscription started'); // [!code ++]
} // [!code ++]
};
function receiveMessage() {
if (data.type == 'subscription' && data.event == 'init') {
console.log('subscription started');
}
}
```
Open your browser, enter your users email and password, and hit submit. Check the browser console. You should see
@@ -217,47 +176,28 @@ Open your browser, enter your users email and password, and hit submit. Check
## Create New Messages
At the top of your component, set up two pieces of state to hold messages: one to keep track of new messages and another
to store an array of previous message history.
At the top of your component, set up a piece of state to store an array of previous message history.
```js
const [newMessage, setNewMessage] = useState('');
const [messageHistory, setMessageHistory] = useState([]);
```
Create a `handleMessageChange` method that updates the value of the message input field as the user types.
```js
const handleMessageChange = (event) => {
setNewMessage(event.target.value);
};
```
Then, connect these values to the form input fields:
```jsx
<form onSubmit={messageSubmit}>
<label htmlFor="message">Message</label>
<input type="text" id="message" /> // [!code --]
<input type="text" id="message" name="message" value={newMessage} onChange={handleMessageChange} /> // [!code ++]
<button type="submit">Submit</button>
</form>
```
Within the `messageSubmit` method, send a new message to create the item in your Directus collection:
```js
const messageSubmit = (event) => {
connectionRef.current.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'create',
data: { text: newMessage },
})
);
event.preventDefault();
setNewMessage('');
const text = event.target.elements.text.value;
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'create',
data: { text },
});
event.target.reset();
};
```
@@ -272,8 +212,16 @@ In your `receiveMessage` function, listen for new `create` events on the `Messag
`messageHistory`:
```js
if (data.type === 'subscription' && data.event === 'create') {
setMessageHistory((history) => [...history, data.data[0]]);
if (data.type == 'subscription' && data.event == 'create') {
addMessageToList(message.data[0]);
}
```
Create an `addMessageToList` function that adds new messages to list:
```js
function addMessageToList(message) {
setMessageHistory([...messageHistory, message]);
}
```
@@ -296,18 +244,51 @@ and navigate to your index.html file, login and submit a message there and both
## Display Historical Messages
Replace the `console.log()` you created when the subscription is initialized:
To display the list of all existing messages, create a function `readAllMessages` with the following:
```js
if (data.type === 'subscription' && data.event === 'init') {
console.log('subscription started'); // [!code --]
for (const message of data.data) { // [!code ++]
setMessageHistory((history) => [...history, message]); // [!code ++]
} // [!code ++]
function readAllMessages() {
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'read',
query: {
limit: 10,
sort: '-date_created',
fields: ['*', 'user_created.first_name'],
},
});
}
```
Run this function directly before subscribing to any events
```js
const cleanup = client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
readAllMessages(); // [!code ++]
subscribe('create');
}
});
```
Within the connection, listen for "items" message to update the user interface with message history.
```js
const cleanup = client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
readAllMessages();
subscribe('create');
}
if (data.type == 'items') { // [!code ++]
for (const item of data.data) { // [!code ++]
addMessageToList(item); // [!code ++]
} // [!code ++]
} // [!code ++]
});
```
Refresh your browser, login, and you should see the existing messages shown in your browser.
## Next Steps
@@ -322,103 +303,121 @@ This guide covers authentication, item creation, and subscription using WebSocke
## Full Code Sample
```jsx
import { useState, useRef } from 'react';
import { authentication, createDirectus, realtime } from '@directus/sdk';
import { useState, useEffect } from 'react';
const url = 'wss://your-directus-url/websocket';
const url = 'https://your-directus-url';
const client = createDirectus(url).with(authentication()).with(realtime());
export default function App() {
const [formValue, setFormValue] = useState({ email: '', password: '' });
const [newMessage, setNewMessage] = useState('');
const [messageHistory, setMessageHistory] = useState([]);
const [messageHistory, setMessageHistory] = useState([]);
const connectionRef = useRef(null);
useEffect(() => {
const cleanup = client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
readAllMessages();
subscribe('create');
}
const authenticate = (opts) => {
const { email, password } = opts;
connectionRef.current.send(JSON.stringify({ type: 'auth', email, password }));
};
if (data.type 'items') {
for (const item of data.data) {
addMessageToList(item);
}
}
});
const loginSubmit = (event) => {
event.preventDefault();
connectionRef.current = new WebSocket(url);
connectionRef.current.addEventListener('open', authenticate(formValue));
connectionRef.current.addEventListener('message', (message) => receiveMessage(message));
};
client.connect();
const receiveMessage = (message) => {
const data = JSON.parse(message.data);
return cleanup;
}, []);
if (data.type == 'auth' && data.status == 'ok') {
connectionRef.current.send(
JSON.stringify({
type: 'subscribe',
collection: 'messages',
query: {
fields: ['*', 'user_created.first_name'],
sort: 'date_created',
},
})
);
}
const loginSubmit = (event) => {
event.preventDefault();
const email = event.target.elements.email.value;
const password = event.target.elements.password.value;
client.login(email, password);
};
if (data.type === 'subscription' && data.event === 'init') {
for (const message of data.data) {
setMessageHistory((history) => [...history, message]);
}
}
async function subscribe(event) {
const { subscription } = await client.subscribe('messages', {
event,
query: {
fields: ['*', 'user_created.first_name'],
},
});
if (data.type === 'subscription' && data.event === 'create') {
setMessageHistory((history) => [...history, data.data[0]]);
}
};
for await (const message of subscription) {
console.log('receiveMessage', message);
receiveMessage(message);
}
}
const messageSubmit = (event) => {
event.preventDefault();
function readAllMessages() {
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'read',
query: {
limit: 10,
sort: '-date_created',
fields: ['*', 'user_created.first_name'],
},
});
}
connectionRef.current.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'create',
data: { text: newMessage },
})
);
function receiveMessage(data) {
if (data.type == 'subscription' && data.event == 'init') {
console.log('subscription started');
}
if (data.type == 'subscription' && data.event == 'create') {
addMessageToList(message.data[0]);
}
}
setNewMessage('');
};
function addMessageToList(message) {
setMessageHistory([...messageHistory, message]);
}
const handleLoginChange = (event) => {
setFormValue({ ...formValue, [event.target.name]: event.target.value });
};
const messageSubmit = (event) => {
event.preventDefault();
const handleMessageChange = (event) => {
setNewMessage(event.target.value);
};
const text = event.target.elements.text.value;
return (
<div className="App">
<form onSubmit={loginSubmit}>
<label htmlFor="email">Email</label>
<input type="email" id="email" name="email" value={formValue.email} onChange={handleLoginChange} />
<label htmlFor="password">Password</label>
<input type="password" id="password" name="password" value={formValue.password} onChange={handleLoginChange} />
<button type="submit">Submit</button>
</form>
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'create',
data: { text },
});
<ol>
{messageHistory.map((message) => (
<li key={message.id}>
{message.user_created.first_name}: {message.text}
</li>
))}
</ol>
event.target.reset();
};
<form onSubmit={messageSubmit}>
<label htmlFor="message">Message</label>
<input type="text" id="message" name="message" value={newMessage} onChange={handleMessageChange} />
<button type="submit">Submit</button>
</form>
</div>
);
return (
<div className='App'>
<form onSubmit={loginSubmit}>
<label htmlFor='email'>Email</label>
<input type='email' id='email' defaultValue='esther@directus.io' />
<label htmlFor='password'>Password</label>
<input type='password' id='password' defaultValue='*yb@o2DtuJvb' />
<input type='submit' />
</form>
<ol>
{messageHistory.map((message) => (
<li key={message.id}>
{message.user_created.first_name}: {message.text}
</li>
))}
</ol>
<form onSubmit={messageSubmit}>
<label htmlFor='message'>Message</label>
<input type='text' id='text' />
<input type='submit' />
</form>
</div>
);
}
```

View File

@@ -1,5 +1,5 @@
---
contributors: Kevin Lewis
contributors: Kevin Lewis, Esther Agbaje
description: Learn how to build a real-time multi-user chat with WebSockets and Vue.js.
---
@@ -17,7 +17,7 @@ You will need a Directus project. If you dont already have one, the easiest w
[managed Directus Cloud service](https://directus.cloud).
Create a new collection called `messages`, with `date_created` and `user_created` fields enabled in the _Optional System
Fields_ pane on collection creation. Create a text field called `text`.
Fields_ pane on collection creation. Create an input field called `text`.
Create a new Role called `Users`, and give Create and Read access to the `Messages` collection, and Read access to the
`Directus Users` system collection. Create a new user with this role. Make note of the password you set.
@@ -29,7 +29,7 @@ Create a new Role called `Users`, and give Create and Read access to the `Messag
<html>
<body>
<div id="app">
<form>
<form @submit.prevent="loginSubmit">
<label for="email">Email</label>
<input type="email" id="email" />
<label for="password">Password</label>
@@ -39,23 +39,15 @@ Create a new Role called `Users`, and give Create and Read access to the `Messag
<ol></ol>
<form>
<form @submit.prevent="messageSubmit">
<label for="message">Message</label>
<input type="text" id="message" />
<input type="text" id="text" />
<input type="submit" />
</form>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
<script setup>
createApp({
data() {
return {};
},
methods: {},
}).mount('#app');
</script>
</body>
</html>
@@ -64,216 +56,131 @@ Create a new Role called `Users`, and give Create and Read access to the `Messag
The first form will handle user login and the second will handle new message submissions. The empty `<ol>` will be
populated with messages.
Inside of Vue's `data` object, create a `url` property being sure to replace `your-directus-url` with your projects
URL:
Inside the setup `script`, create a `url` property being sure to replace `your-directus-url` with your projects URL:
```js
data() {
return {
url: 'wss://your-directus-url/websocket', // [!code ++]
connection: null, // [!code ++]
};
},
const url = 'https://your-directus-url';
```
The `connection` property will later contain a WebSocket instance.
## Import the Required Composables and Methods
At the top of your setup `script`, import the SDK composables and vue hooks needed for this project
```js
import { onMounted, ref, onBeforeUnmount } from 'vue';
import { authentication, createDirectus, realtime } from '@directus/sdk';
```
- `createDirectus` is a function that initializes a Directus client.
- `authentication` provides methods to authenticate a user.
- `realtime` provides methods to establish a WebSocket connection.
## Establish and Authenticate a WebSocket Client
```js
const client = createDirectus(url)
.with(authentication())
.with(realtime());
```
## Set Up Form Submission Methods
Create the methods for form submissions:
```js
methods: {
loginSubmit() { // [!code ++]
}, // [!code ++]
messageSubmit() { // [!code ++]
}, // [!code ++]
const loginSubmit = (event) => {};
const messageSubmit = (event) => {};
```
Now, extract the `email` and `password` values from the login form.
```js
const loginSubmit = (event) => {
const email = event.target.elements.email.value; // [!code ++]
const password = event.target.elements.password.value; // [!code ++]
};
```
Once the client is authenticated, immediately create a WebSocket connection:
```js
client.connect();
```
## Subscribe To Messages
As soon as you have successfully authenticated, a message will be sent. When this happens, within `onMounted` hook,
subscribe to updates on the `Messages` collection.
```js
onMounted(() => {
const cleanup = client.onWebSocket('message', function (message) {
if (message.type == 'auth' && message.status == 'ok') {
subscribe('create');
}
});
client.connect();
onBeforeUnmount(cleanup);
});
```
Create a `subscribe` function that subscribes to events.
```js
async function subscribe(event) {
const { subscription } = await client.subscribe('messages', {
event,
query: {
fields: ['*', 'user_created.first_name'],
},
});
for await (const message of subscription) {
receiveMessage(message);
}
}
```
Then, ensure these methods are called on form submissions by using the `@submit.prevent` directive:
```vue-html
<!-- Login form -->
<form> // [!code --]
<form @submit.prevent="loginSubmit"> // [!code ++]
<label for="email">Email</label>
<!-- Message form -->
<form> // [!code --]
<form @submit.prevent="messageSubmit"> // [!code ++]
<label for="message">Message</label>
```
## Establish WebSocket Connection
First, create a new `login` object in your Vue application's `data` object. It will contain form data:
When a subscription is started, a message will be sent to confirm. Create a `receiveMessage` function with the
following:
```js
data() {
return {
url: 'wss://your-directus-url/websocket',
connection: null,
form: {}, // [!code ++]
};
},
```
Then, bind the login form's inputs to `form`:
```vue-html
<form>
<label for="email">Email</label>
<input type="email" id="email"> // [!code --]
<input v-model="form.email" type="email" id="email"> // [!code ++]
<label for="password">Password</label>
<input type="password" id="password"> // [!code --]
<input v-model="form.password" type="password" id="password"> // [!code ++]
<input type="submit">
</form>
```
Within the `loginSubmit` method, create a new WebSocket, which will immediately attempt connection:
```js
loginSubmit() {
this.connection = new WebSocket(this.url); // [!code ++]
},
```
On connection, you must [send an authentication message before the timeout](/guides/real-time/authentication). Add an
event handler for the connection's `open` event:
```js
loginSubmit() {
this.connection = new WebSocket(this.url);
this.connection.addEventListener('open', () => this.authenticate(this.form)); // [!code ++]
},
```
Then, create a new `authenticate` method:
```js
authenticate(opts) {
const { email, password } = opts;
this.connection.send(JSON.stringify({ type: 'auth', email, password }));
},
```
### Subscribe to messages
In a WebSocket connection, all data sent from the server will trigger the connections `message` event. Inside
`loginSubmit`, add an event handler:
```js
loginSubmit() {
this.connection = new WebSocket(this.url);
this.connection.addEventListener('open', () => this.authenticate(this.form));
this.connection.addEventListener('message', (message) => this.receiveMessage(message)); // [!code ++]
},
```
Then, create a new `receiveMessage` method:
```js
receiveMessage(message) {
const data = JSON.parse(message.data);
function receiveMessage() {
if (data.type == 'subscription' && data.event == 'init') {
console.log('subscription started');
}
}
```
As soon as you have successfully authenticated, a message will be sent. When this happens, subscribe to updates on the
`Messages` collection. Add this inside of the `receiveMessage` method:
```js
receiveMessage(message) {
const data = JSON.parse(message.data);
// [!code ++]
if (data.type == 'auth' && data.status == 'ok') { // [!code ++]
connection.send(JSON.stringify({ // [!code ++]
type: 'subscribe', // [!code ++]
collection: 'messages', // [!code ++]
query: { // [!code ++]
fields: ['text', 'user_created.first_name'], // [!code ++]
sort: 'date_created' // [!code ++]
} // [!code ++]
})); // [!code ++]
} // [!code ++]
}
```
When a subscription is started, a message will be sent to confirm. Add this inside of the `receiveMessage` method:
```js
receiveMessage(message) {
const data = JSON.parse(message.data);
if (data.type == 'auth' && data.status == 'ok') {
this.connection.send(
JSON.stringify({
type: 'subscribe',
collection: 'messages',
query: {
fields: ['text', 'user_created.first_name'],
sort: 'date_created',
},
})
);
}
// [!code ++]
if (data.type == 'subscription' && data.event == 'init') { // [!code ++]
console.log('subscription started'); // [!code ++]
} // [!code ++]
}
```
_Open your `index.html` file in your browser, enter your users email and password, submit, and check the browser
console for this console log._
Open your browser, enter your users email and password, and hit submit. Check the browser console. You should see
“subscription started”
## Create New Messages
First, create a new `messages` object in your Vue application's `data` object with two properties: a `new` string to be
bound to the input, and a `history` array to contain existing messages:
At the top of your component, set up a ref to store an array of previous message history.
```js
data() {
return {
url: 'wss://your-directus-url/websocket',
connection: null,
form: {},
messages: { // [!code ++]
new: '', // [!code ++]
history: [], // [!code ++]
}, // [!code ++]
};
},
```
Then, bind the login form's inputs to `form` properties:
```vue-html
<form @submit.prevent="messageSubmit">
<label for="message">Message</label>
<input type="text" id="message"> // [!code --]
<input v-model="messages.new" type="text" id="message"> // [!code ++]
<input type="submit">
</form>
const messageHistory = ref([]);
```
Within the `messageSubmit` method, send a new message to create the item in your Directus collection:
```js
messageSubmit() {
this.connection.send( // [!code ++]
JSON.stringify({ // [!code ++]
type: 'items', // [!code ++]
collection: 'messages', // [!code ++]
action: 'create', // [!code ++]
data: { text: this.messages.new }, // [!code ++]
}) // [!code ++]
); // [!code ++]
// [!code ++]
this.messages.new = ''; // [!code ++]
}
const messageSubmit = (event) => {
const text = event.target.elements.text.value;
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'create',
data: { text },
});
event.target.reset();
};
```
_Refresh your browser, login, and submit a new message. Check the `Messages` collection in your Directus project and you
@@ -284,21 +191,29 @@ should see a new item._
## Display New Messages
In your `receiveMessage` function, listen for new `create` events on the `Messages` collection, and add them to
`messages.history`:
`messageHistory`:
```js
if (data.type == 'subscription' && data.event == 'create') {
this.messages.history.push(data.data[0]);
addMessageToList(message.data[0]);
}
```
Create an `addMessageToList` function that adds new messages to list:
```js
function addMessageToList(message) {
messageHistory.value.push(message);
}
```
Update your `<ol>` to display items in the array:
Update your `<ol>` to display items in the array by mapping over `messageHistory`
```vue-html
```js
<ol>
<li v-for="message in messages.history" :key="message.id"> // [!code ++]
{{ message.user_created.first_name }}: {{ message.text }} // [!code ++]
</li> // [!code ++]
<li v-for="message in messageHistory" :key="message.id">
{{ message.user_created.first_name }}: {{ message.text }}
</li>
</ol>
```
@@ -309,17 +224,51 @@ and navigate to your index.html file, login and submit a message there and both
## Display Historical Messages
Replace the `console.log()` you created when the subscription is initialized:
To display the list of all existing messages, create a function `readAllMessages` with the following:
```js
if (data.type == 'subscription' && data.event == 'init') {
console.log('subscription started'); // [!code --]
for (const message of data.data) { // [!code ++]
this.messages.history.push(message); // [!code ++]
} // [!code ++]
function readAllMessages() {
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'read',
query: {
limit: 10,
sort: '-date_created',
fields: ['*', 'user_created.first_name'],
},
});
}
```
Invoke this function directly before subscribing to any events
```js
const cleanup = client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
readAllMessages(); // [!code ++]
subscribe('create');
}
});
```
Within the connection, listen for "items" message to update the user interface with message history.
```js
const cleanup = client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
readAllMessages();
subscribe('create');
}
if (data.type == 'items') { // [!code ++]
for (const item of data.data) { // [!code ++]
addMessageToList(item); // [!code ++]
} // [!code ++]
} // [!code ++]
});
```
Refresh your browser, login, and you should see the existing messages shown in your browser.
## Next Steps
@@ -336,96 +285,119 @@ This guide covers authentication, item creation, and subscription using WebSocke
```html
<!DOCTYPE html>
<html>
<body>
<div id="app">
<form @submit.prevent="loginSubmit">
<label for="email">Email</label>
<input v-model="form.email" type="email" id="email" />
<label for="password">Password</label>
<input v-model="form.password" type="password" id="password" />
<input type="submit" />
</form>
<body>
<div id="app">
<form @submit.prevent="loginSubmit">
<label for="email">Email</label>
<input v-model="form.email" type="email" id="email" />
<label for="password">Password</label>
<input v-model="form.password" type="password" id="password" />
<input type="submit" />
</form>
<ol>
<li v-for="message in messages.history" :key="message.id">
{{ message.user_created.first_name }}: {{ message.text }}
</li>
</ol>
<ol>
<li v-for="message in messages.history" :key="message.id">
{{ message.user_created.first_name }}: {{ message.text }}
</li>
</ol>
<form @submit.prevent="messageSubmit">
<label for="message">Message</label>
<input v-model="messages.new" type="text" id="message" />
<input type="submit" />
</form>
</div>
<form @submit.prevent="messageSubmit">
<label for="message">Message</label>
<input v-model="messages.new" type="text" id="text" />
<input type="submit" />
</form>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
<script>
import { onMounted, ref, onBeforeUnmount } from 'vue';
import { authentication, createDirectus, realtime } from '@directus/sdk';
createApp({
data() {
return {
url: 'wss://your-directus-url/websocket',
connection: null,
form: {},
messages: {
new: '',
history: [],
},
};
},
methods: {
loginSubmit() {
this.connection = new WebSocket(this.url);
this.connection.addEventListener('open', () => this.authenticate(this.form));
this.connection.addEventListener('message', (message) => this.receiveMessage(message));
},
messageSubmit() {
this.connection.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'create',
data: { text: this.messages.new },
})
);
const messageHistory = ref([]);
this.messages.new = '';
},
authenticate(opts) {
const { email, password } = opts;
this.connection.send(JSON.stringify({ type: 'auth', email, password }));
},
receiveMessage(message) {
const data = JSON.parse(message.data);
const url = 'https://your-directus-url';
if (data.type == 'auth' && data.status == 'ok') {
this.connection.send(
JSON.stringify({
type: 'subscribe',
collection: 'messages',
query: {
fields: ['text', 'user_created.first_name'],
sort: 'date_created',
},
})
);
}
const client = createDirectus(url)
.with(authentication())
.with(realtime());
if (data.type == 'subscription' && data.event == 'init') {
for (const message of data.data) {
this.messages.history.push(message);
}
}
onMounted(() => {
const cleanup = client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
readAllMessages();
subscribe('create');
}
if (data.type == 'subscription' && data.event == 'create') {
this.messages.history.push(data.data[0]);
}
},
},
}).mount('#app');
</script>
</body>
if (data.type == 'items') {
for (const item of data.data) {
addMessageToList(item);
}
}
console.log(message);
});
client.connect();
onBeforeUnmount(cleanup);
});
const loginSubmit = (event) => {
const email = event.target.elements.email.value;
const password = event.target.elements.password.value;
client.login(email, password);
};
async function subscribe(event) {
const { subscription } = await client.subscribe('messages', {
event,
query: {
fields: ['*', 'user_created.first_name'],
},
});
for await (const message of subscription) {
console.log('receiveMessage', message);
receiveMessage(message);
}
}
function readAllMessages() {
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'read',
query: {
limit: 10,
sort: '-date_created',
fields: ['*', 'user_created.first_name'],
},
});
}
function receiveMessage(data) {
if (data.type == 'subscription' && data.event == 'init') {
console.log('subscription started');
}
if (data.type == 'subscription' && data.event == 'create') {
addMessageToList(message.data[0]);
}
}
function addMessageToList(message) {
messageHistory.value.push(message);
}
const messageSubmit = (event) => {
const text = event.target.elements.text.value;
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'create',
data: { text },
});
event.target.reset();
};
</script>
</body>
</html>
```

View File

@@ -0,0 +1,287 @@
---
contributors: Kevin Lewis
description: "Learn how to get started with Directus' WebSockets interface."
---
# Getting Started With WebSockets
You can connect to a Directus project using a WebSocket interface and get updates on data held in a collection in
real-time.
This guide will show you how to get started with Directus' WebSockets interface and JavaScript. WebSockets are
language-agnostic, so you can apply the same set of steps in your stack of choice.
## Before You Begin
You will need a Directus project. If you dont already have one, the easiest way to get started is with our
[managed Directus Cloud service](https://directus.cloud). You can also self-host Directus, ensuring the
`WEBSOCKETS_ENABLED` environment variable is set to `true`.
Create a new collection called `messages`, with a `date_created` field enabled in the _Optional Fields_ pane on
collection creation. Create an input field called `text` and a second called `user`.
If it doesnt already exist, create a user with a role that can execute read and create operations on the collection.
Finally in the Directus Data Studio, create a static access token for the user, copy it, and save the user profile.
Create an `index.html` file and open it in your code editor. Add the following boilerplate code:
```html
<!DOCTYPE html>
<html>
<body>
<script>
const url = 'wss://your-directus-url/websocket';
const access_token = 'your-access-token';
const collection = 'messages';
</script>
</body>
</html>
```
Make sure to replace `your-directus-url` and `your-access-token` with your project and user details.
## Create a Connection
At the bottom of your `<script>`, add the following code to establish a new WebSocket connection:
```js
const connection = new WebSocket(url);
```
To add some feedback, add the following event handlers below your `connection` variable:
```js
connection.addEventListener('open', function () {
console.log({ event: 'onopen' });
});
connection.addEventListener('message', function (message) {
const data = JSON.parse(message.data);
console.log({ event: 'onmessage', data });
});
connection.addEventListener('close', function () {
console.log({ event: 'onclose' });
});
connection.addEventListener('error', function (error) {
console.log({ event: 'onerror', error });
});
```
Open `index.html` in your browser and open the Developer Tools. You should see the `onopen` event logged in the console.
## Authenticate Your Connection
Once a connection is opened, you have to send a message to authenticate your session. If you don't, you'll receive a
message indicating there was an authentication failure.
```js
connection.addEventListener('open', function () {
console.log({ event: 'onopen' });
// [!code ++]
connection.send( // [!code ++]
JSON.stringify({ // [!code ++]
type: 'auth', // [!code ++]
access_token, // [!code ++]
}) // [!code ++]
); // [!code ++]
});
```
You should immediately receive a message in return to confirm. The connection is now authenticated and will remain open,
ready to send and receive data.
[Learn more about WebSocket authentication here.](/guides/real-time/authentication)
## Create a Subscription
After subscribing to collections over your connection, you will receive new messages whenever items in the collection
are created, updated, or deleted.
At the bottom of your `<script>`, create a new function which subscribes to a collection:
```js
function subscribe() {
connection.send(
JSON.stringify({
type: 'subscribe',
collection: 'messages',
query: { fields: ['*'] },
})
);
}
```
Save your file, refresh your browser, and open your browser console. Run this function by typing:
```js
subscribe();
```
You will receive a message in response to confirm the subscription has been initialized. Then, new messages will be sent
when theres an update on the collection.
## Create Item
Create a new function that sends a message over the connection with a `create` action:
```js
function createItem(text, user) {
connection.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'create',
data: { text, user },
})
);
}
```
Save your file, refresh your browser, and open your browser console. Create a few new items by using your new function
directly in the console:
```js
createItem('Hello World!', 'Ben');
createItem('Hello Universe!', 'Rijk');
createItem('Hello Everyone Everywhere All At Once!', 'Kevin');
```
Every time you create an item, you will receive a message in response with the new item as created in your Directus
collection.
![Directus Data Studio Content Module showing the Messages collection with three items in it - one for each time the above command was run in the console.](https://cdn.directus.io/docs/v9/guides/websockets/getting-started-messages-collection.webp)
## Get Latest Item
You can use your connection to perform all CRUD actions by using `type: 'items'` in the payload and including the
respective `action`. Create a new function for reading the latest message:
```js
function readLatestItem() {
connection.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'read',
query: { limit: 1, sort: '-date_created' },
})
);
}
```
Send the message over the connection by entering `readLatestItem()` your browser console. You will receive a message
with the result of your query on the collection.
## Pings To Keep Connection Active
You may have noticed that, periodically, you will receive a message with a type of `ping`. This serves two purposes:
1. To act as a periodic message to stop your connection from closing due to inactivity. This may be required by your
application technology stack.
2. To verify that the connection is still active.
In order to prevent the connection from _closing_, you may reply with a `pong` event:
```js
// Exemplary code
connection.addEventListener('message', (message) => {
const data = JSON.parse(message.data);
if (data.type === 'ping') {
this.connection.send(
JSON.stringify({
type: 'pong',
}),
);
}
});
```
On Directus Cloud, this feature is enabled. If you are self-hosting, you can alter this behavior with the
`WEBSOCKETS_HEARTBEAT_ENABLED` and `WEBSOCKETS_HEARTBEAT_PERIOD` environment variables.
You may wish to exclude these messages from your application logic.
## In Summary
In this guide, you have successfully created a new WebSocket connection, authenticated yourself, and performed CRUD
operations over the connection. You have also created your first subscription.
[Learn more about subscriptions with WebSockets with Directus.](/guides/real-time/subscriptions/websockets)
## Full Code Sample
```html
<!DOCTYPE html>
<html>
<body>
<script>
const url = 'wss://your-directus-url/websocket';
const access_token = 'your-access-token';
const collection = 'messages';
const connection = new WebSocket(url);
connection.addEventListener('open', function () {
console.log({ event: 'onopen' });
connection.send(
JSON.stringify({
type: 'auth',
access_token,
})
);
});
connection.addEventListener('message', function (message) {
const data = JSON.parse(message.data);
console.log({ event: 'onmessage', data });
});
connection.addEventListener('close', function () {
console.log({ event: 'onclose' });
});
connection.addEventListener('error', function (error) {
console.log({ event: 'onerror', error });
});
function subscribe() {
connection.send(
JSON.stringify({
type: 'subscribe',
collection: 'messages',
query: {
fields: ['*'],
},
})
);
}
function createItem(text, user) {
connection.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'create',
data: { text, user },
})
);
}
function readLatestItem() {
connection.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'read',
query: { limit: 1, sort: '-date_created' },
})
);
}
</script>
</body>
</html>
```

View File

@@ -1,16 +1,23 @@
---
contributors: Kevin Lewis
contributors: Kevin Lewis, Esther Agbaje
description: "Learn how to get started with Directus' WebSockets interface."
---
# Getting Started With WebSockets
# Getting Started with WebSockets and the Directus SDK
You can connect to a Directus project using a WebSocket interface and get updates on data held in a collection in
real-time.
This guide will show you how to get started with Directus' WebSockets interface and JavaScript. WebSockets are
This guide will show you how to get started with Directus' Realtime SDK and JavaScript. WebSockets are
language-agnostic, so you can apply the same set of steps in your stack of choice.
::: info Want to use WebSockets without the SDK?
To get started with WebSockets without the SDK please refer to our
[Getting Started with WebSockets guide](/guides/real-time/getting-started/websockets-js.md).
:::
## Before You Begin
You will need a Directus project. If you dont already have one, the easiest way to get started is with our
@@ -18,9 +25,10 @@ You will need a Directus project. If you dont already have one, the easiest w
`WEBSOCKETS_ENABLED` environment variable is set to `true`.
Create a new collection called `messages`, with a `date_created` field enabled in the _Optional Fields_ pane on
collection creation. Create a text field called `text` and a second called `user`.
collection creation. Create an input field called `text` and a second called `user`.
If it doesnt already exist, create a user with a role that can execute read and create operations on the collection.
If it doesnt already exist, create a user with a role that can execute **read**, **create** and **update** operations
on the collection.
Finally in the Directus Data Studio, create a static access token for the user, copy it, and save the user profile.
@@ -41,57 +49,70 @@ Create an `index.html` file and open it in your code editor. Add the following b
Make sure to replace `your-directus-url` and `your-access-token` with your project and user details.
## Create a Connection
## Import the SDK Composables
At the bottom of your `<script>`, add the following code to establish a new WebSocket connection:
At the top of the `<script>` tag, import the composables needed for the SDK
```js
const connection = new WebSocket(url);
```html
<!doctype html>
<html>
<body>
<script>
import { createDirectus, staticToken, realtime } from 'https://www.unpkg.com/@directus/sdk/dist/index.js'; // [!code ++]
const url = 'wss://your-directus-url/websocket';
const access_token = 'your-access-token';
const collection = 'messages';
</script>
</body>
</html>
```
To add some feedback, add the following event handlers below your `connection` variable:
- `staticToken` is used to authenticate the client we'll create shortly.
- `createDirectus` is a function that initializes a Directus client.
- `realtime` provides methods to establish a WebSocket connection.
## Create a Realtime Client with Authentication
To authenticate the client using a static token and receive realtime updates about changes in your data, add the
following code at the bottom of your `<script>` to create a client:
```js
connection.addEventListener('open', function () {
const client = createDirectus(url)
.with(staticToken(access_token))
.with(realtime());
```
Establish a connection:
```js
await client.connect();
```
To listen for when changes are made to an item in your data, use the `onWebSocket` method
```js
client.onWebSocket('open', function () {
console.log({ event: 'onopen' });
});
connection.addEventListener('message', function (message) {
const data = JSON.parse(message.data);
client.onWebSocket('message', function (message) {
const { type, data } = message;
console.log({ event: 'onmessage', data });
});
connection.addEventListener('close', function () {
client.onWebSocket('close', function () {
console.log({ event: 'onclose' });
});
connection.addEventListener('error', function (error) {
client.onWebSocket('error', function (error) {
console.log({ event: 'onerror', error });
});
```
Open `index.html` in your browser and open the Developer Tools. You should see the `onopen` event logged in the console.
## Authenticate Your Connection
Once a connection is opened, you have to send a message to authenticate your session. If you don't, you'll receive a
message indicating there was an authentication failure.
```js
connection.addEventListener('open', function () {
console.log({ event: 'onopen' });
// [!code ++]
connection.send( // [!code ++]
JSON.stringify({ // [!code ++]
type: 'auth', // [!code ++]
access_token, // [!code ++]
}) // [!code ++]
); // [!code ++]
});
```
You should immediately receive a message in return to confirm. The connection is now authenticated and will remain open,
ready to send and receive data.
Open `index.html` in your browser and open the Developer Tools. You should immediately receive a message in return to
confirm. The connection is now authenticated and will remain open, ready to send and receive data.
[Learn more about WebSocket authentication here.](/guides/real-time/authentication)
@@ -100,26 +121,25 @@ ready to send and receive data.
After subscribing to collections over your connection, you will receive new messages whenever items in the collection
are created, updated, or deleted.
At the bottom of your `<script>`, create a new function which subscribes to a collection:
At the bottom of your `<script>`, create a new function `subscribe` which subscribes to a collection:
```js
function subscribe() {
connection.send(
JSON.stringify({
type: 'subscribe',
collection: 'messages',
query: { fields: ['*'] },
})
);
async function subscribe() {
const { subscription } = await client.subscribe('messages', {
event: 'update',
query: { fields: ['user', 'text'] },
});
for await (const item of subscription) {
console.log(item);
}
}
```
Save your file, refresh your browser, and open your browser console. Run this function by typing:
```js
subscribe();
```
Save your file, refresh your browser, and open your browser console.
You will receive a message in response to confirm the subscription has been initialized. Then, new messages will be sent
when theres an update on the collection.
@@ -129,14 +149,12 @@ Create a new function that sends a message over the connection with a `create` a
```js
function createItem(text, user) {
connection.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'create',
data: { text, user },
})
);
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'create',
data: { text, user },
});
}
```
@@ -161,14 +179,12 @@ respective `action`. Create a new function for reading the latest message:
```js
function readLatestItem() {
connection.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'read',
query: { limit: 1, sort: '-date_created' },
})
);
client.sendMessage({
type: 'items',
collection: 'messages',
action: 'read',
query: { limit: 1, sort: '-date_created' },
});
}
```
@@ -183,20 +199,15 @@ You may have noticed that, periodically, you will receive a message with a type
application technology stack.
2. To verify that the connection is still active.
In order to prevent the connection from _closing_, you may reply with a `pong` event:
In order to prevent the connection from closing, you may reply with a pong event:
```js
// Exemplary code
connection.addEventListener('message', (message) => {
const data = JSON.parse(message.data);
if (data.type === 'ping') {
this.connection.send(
JSON.stringify({
type: 'pong',
}),
);
}
client.onWebSocket('message', (message) => {
if (message.type === 'ping') {
client.sendMessage({
type: 'pong',
});
}
});
```
@@ -217,71 +228,72 @@ operations over the connection. You have also created your first subscription.
```html
<!DOCTYPE html>
<html>
<body>
<script>
const url = 'wss://your-directus-url/websocket';
const access_token = 'your-access-token';
const collection = 'messages';
<body>
<script>
import {
createDirectus,
staticToken,
realtime,
} from 'https://www.unpkg.com/@directus/sdk/dist/index.js';
const connection = new WebSocket(url);
const url = 'wss://your-directus-url/websocket';
const access_token = 'your-access-token';
const collection = 'messages';
connection.addEventListener('open', function () {
console.log({ event: 'onopen' });
connection.send(
JSON.stringify({
type: 'auth',
access_token,
})
);
});
const client = createDirectus(url)
.with(staticToken(access_token))
.with(realtime());
connection.addEventListener('message', function (message) {
const data = JSON.parse(message.data);
console.log({ event: 'onmessage', data });
});
await client.connect();
connection.addEventListener('close', function () {
console.log({ event: 'onclose' });
});
client.onWebSocket('open', function () {
console.log({ event: 'onopen' });
});
connection.addEventListener('error', function (error) {
console.log({ event: 'onerror', error });
});
client.onWebSocket('message', function (message) {
const { type, data } = message;
if (message.type == 'auth' && message.status == 'ok') {
subscribe();
console.log({ event: 'onmessage', data });
}
function subscribe() {
connection.send(
JSON.stringify({
type: 'subscribe',
collection: 'messages',
query: {
fields: ['*'],
},
})
);
}
});
function createItem(text, user) {
connection.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'create',
data: { text, user },
})
);
}
client.onWebSocket('close', function () {
console.log({ event: 'onclose' });
});
function readLatestItem() {
connection.send(
JSON.stringify({
type: 'items',
collection: 'messages',
action: 'read',
query: { limit: 1, sort: '-date_created' },
})
);
}
</script>
</body>
client.onWebSocket('error', function (error) {
console.log({ event: 'onerror', error });
});
const { subscription } = await client.subscribe(collection, {
event: 'update',
query: { fields: ['user', 'text'] },
});
for await (const item of subscription) {
console.log(item);
}
function createItem(text, user) {
client.sendMessage({
type: 'items',
collection: collection,
action: 'create',
data: { text, user },
});
}
function readLatestItem() {
client.sendMessage({
type: 'items',
collection: collection,
action: 'read',
query: { limit: 1, sort: '-date_created' },
});
}
</script>
</body>
</html>
```

View File

@@ -78,40 +78,44 @@ Also create a `results.html` file and open it in your editor. Add the following:
```html
<!DOCTYPE html>
<html>
<body>
<div style="display: flex; justify-content: center; height: 80vh">
<canvas id="chart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const socket = new WebSocket('wss://your-directus-url/websocket');
const access_token = 'your-access-token';
<body>
<div style="display: flex; justify-content: center; height: 80vh">
<canvas id="chart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script type="module">
import {
createDirectus,
realtime,
authentication,
staticToken,
} from 'https://www.unpkg.com/@directus/sdk/dist/index.js';
socket.addEventListener('open', function () {
console.log({ event: 'onopen' });
const url = 'https://your-directus-url/';
const access_token = 'your-access-token';
socket.send(
JSON.stringify({
type: 'auth',
access_token,
})
);
});
const client = createDirectus(url)
.with(staticToken(access_token))
.with(realtime());
socket.addEventListener('message', function (message) {
const data = JSON.parse(message.data);
client.connect();
if (data.type == 'auth' && data.status == 'ok') {
}
client.onWebSocket('open', function () {
console.log({ event: 'onopen' });
});
if (data.type == 'subscription' && data.event == 'init') {
}
client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
}
if (data.type == 'subscription' && data.event == 'create') {
}
});
</script>
</body>
if (data.type == 'subscription' && data.event == 'init') {
}
if (data.type == 'subscription' && data.event == 'create') {
}
});
</script>
</body>
</html>
```
@@ -152,8 +156,7 @@ Once authenticated, immediately subscribe to the `votes` collection:
```js
if (data.type == 'auth' && data.status == 'ok') {
socket.send( // [!code ++]
JSON.stringify({ // [!code ++]
client.sendMessage({ // [!code ++]
type: 'subscribe', // [!code ++]
collection: 'votes', // [!code ++]
query: { // [!code ++]
@@ -161,7 +164,6 @@ if (data.type == 'auth' && data.status == 'ok') {
groupBy: ['choice'], // [!code ++]
}, // [!code ++]
}) // [!code ++]
); // [!code ++]
}
```
@@ -256,82 +258,84 @@ There are many ways to improve the project built in this guide:
```html
<!DOCTYPE html>
<html>
<body>
<div style="display: flex; justify-content: center; height: 80vh">
<canvas id="chart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const socket = new WebSocket('wss://your-directus-url/websocket');
const access_token = 'your-access-token';
<body>
<div style="display: flex; justify-content: center; height: 80vh">
<canvas id="chart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script type="module">
import {
createDirectus,
realtime,
authentication,
staticToken,
} from 'https://www.unpkg.com/@directus/sdk/dist/index.js';
socket.addEventListener('open', function () {
console.log({ event: 'onopen' });
const url = 'https://your-directus-url/';
const access_token = 'your-access-token';
socket.send(
JSON.stringify({
type: 'auth',
access_token,
})
);
});
const client = createDirectus(url)
.with(staticToken(access_token))
.with(realtime());
socket.addEventListener('message', function (message) {
const data = JSON.parse(message.data);
client.connect();
if (data.type == 'auth' && data.status == 'ok') {
socket.send(
JSON.stringify({
type: 'subscribe',
collection: 'votes',
query: {
aggregate: { count: 'choice' },
groupBy: ['choice'],
},
})
);
}
client.onWebSocket('open', function () {
console.log({ event: 'onopen' });
});
if (data.type == 'subscription' && data.event == 'init') {
for (const item of data.data) {
chart.data.labels.push(item.choice);
chart.data.datasets[0].data.push(item.count.choice);
}
client.onWebSocket('message', function (data) {
if (data.type == 'auth' && data.status == 'ok') {
client.sendMessage({
type: 'subscribe',
collection: 'votes',
query: {
aggregate: { count: 'choice' },
groupBy: ['choice'],
},
});
}
chart.update();
}
if (data.type == 'subscription' && data.event == 'init') {
for (const item of data.data) {
chart.data.labels.push(item.choice);
chart.data.datasets[0].data.push(item.count.choice);
}
if (data.type == 'subscription' && data.event == 'create') {
const vote = data.data[0];
const itemToUpdate = chart.data.labels.indexOf(vote.choice);
chart.update();
}
if (itemToUpdate !== -1) {
chart.data.datasets[0].data[itemToUpdate]++;
} else {
chart.data.labels.push(vote.choice);
chart.data.datasets[0].data.push(1);
}
if (data.type == 'subscription' && data.event == 'create') {
const vote = data.data[0];
const itemToUpdate = chart.data.labels.indexOf(vote.choice);
chart.update();
}
});
if (itemToUpdate !== -1) {
chart.data.datasets[0].data[itemToUpdate]++;
} else {
chart.data.labels.push(vote.choice);
chart.data.datasets[0].data.push(1);
}
const ctx = document.getElementById('chart');
chart.update();
}
});
const chart = new Chart(ctx, {
type: 'pie',
data: {
labels: [],
datasets: [
{
label: '# of Votes',
data: [],
backgroundColor: ['#4f46e5', '#f472b6'],
},
],
},
});
</script>
</body>
const ctx = document.getElementById('chart');
const chart = new Chart(ctx, {
type: 'pie',
data: {
labels: [],
datasets: [
{
label: '# of Votes',
data: [],
backgroundColor: ['#4f46e5', '#f472b6'],
},
],
},
});
</script>
</body>
</html>
```