mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
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:
@@ -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 don’t 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 project’s 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 connection’s `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>
|
||||
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@ You will need a Directus project. If you don’t 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 project’s 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 connection’s `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 user’s email and password, and hit submit. Check the browser console. You should see
|
||||
@@ -217,47 +176,28 @@ Open your browser, enter your user’s 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>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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 don’t 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 project’s
|
||||
URL:
|
||||
Inside the setup `script`, create a `url` property being sure to replace `your-directus-url` with your project’s 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 connection’s `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 user’s email and password, submit, and check the browser
|
||||
console for this console log._
|
||||
Open your browser, enter your user’s 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>
|
||||
```
|
||||
|
||||
287
docs/guides/real-time/getting-started/websockets-js.md
Normal file
287
docs/guides/real-time/getting-started/websockets-js.md
Normal 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 don’t 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 doesn’t 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 there’s 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.
|
||||
|
||||

|
||||
|
||||
## 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>
|
||||
```
|
||||
@@ -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 don’t already have one, the easiest way to get started is with our
|
||||
@@ -18,9 +25,10 @@ You will need a Directus project. If you don’t 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 doesn’t already exist, create a user with a role that can execute read and create operations on the collection.
|
||||
If it doesn’t 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 there’s 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>
|
||||
```
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user