From 64fd6701d80f631419f31957ee4efe0cff25f656 Mon Sep 17 00:00:00 2001 From: Esther Agbaje <53586167+estheragbaje@users.noreply.github.com> Date: Fri, 26 Jan 2024 09:12:08 +0000 Subject: [PATCH] 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 * 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 * fix indentation * remove console.log * Update docs/guides/real-time/chat/javascript.md Co-authored-by: Kevin Lewis * Update docs/guides/real-time/chat/javascript.md Co-authored-by: Kevin Lewis * Update docs/guides/real-time/chat/javascript.md Co-authored-by: Kevin Lewis * removed strict equality * Update docs/guides/real-time/chat/javascript.md Co-authored-by: Kevin Lewis * add the JS ; * Update docs/guides/real-time/chat/react.md Co-authored-by: Kevin Lewis * 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 * Update docs/guides/real-time/getting-started/websockets.md Co-authored-by: Kevin Lewis * tiny text updates --------- Co-authored-by: Kevin Lewis Co-authored-by: Pascal Jufer Co-authored-by: Brainslug --- docs/guides/real-time/chat/javascript.md | 352 ++++++----- docs/guides/real-time/chat/react.md | 459 ++++++++------- docs/guides/real-time/chat/vue.md | 556 +++++++++--------- .../getting-started/websockets-js.md | 287 +++++++++ .../real-time/getting-started/websockets.md | 284 ++++----- docs/guides/real-time/live-poll.md | 198 ++++--- 6 files changed, 1237 insertions(+), 899 deletions(-) create mode 100644 docs/guides/real-time/getting-started/websockets-js.md diff --git a/docs/guides/real-time/chat/javascript.md b/docs/guides/real-time/chat/javascript.md index 5992500487..aa56e0dc3d 100644 --- a/docs/guides/real-time/chat/javascript.md +++ b/docs/guides/real-time/chat/javascript.md @@ -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: - +
    @@ -43,7 +43,7 @@ Create an `index.html` file and open it in your code editor:
    - +
    @@ -57,13 +57,42 @@ populated with messages. Inside of the ` + + +``` + +- `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 ` - + 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); + } + + + ``` diff --git a/docs/guides/real-time/chat/react.md b/docs/guides/real-time/chat/react.md index 5f5a2dbc46..c045290833 100644 --- a/docs/guides/real-time/chat/react.md +++ b/docs/guides/real-time/chat/react.md @@ -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() {
    - +
    @@ -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 -
    - - // [!code --] - // [!code ++] - - // [!code --] - // [!code ++] - -
    -``` - -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 -
    - - // [!code --] - // [!code ++] - -
    -``` - 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 ( -
    -
    - - - - - -
    + client.sendMessage({ + type: 'items', + collection: 'messages', + action: 'create', + data: { text }, + }); -
      - {messageHistory.map((message) => ( -
    1. - {message.user_created.first_name}: {message.text} -
    2. - ))} -
    + event.target.reset(); + }; -
    - - - -
    -
    - ); + return ( +
    +
    + + + + + +
    + +
      + {messageHistory.map((message) => ( +
    1. + {message.user_created.first_name}: {message.text} +
    2. + ))} +
    + +
    + + + +
    +
    + ); } ``` diff --git a/docs/guides/real-time/chat/vue.md b/docs/guides/real-time/chat/vue.md index 8d81aeeccd..dcbaa6acca 100644 --- a/docs/guides/real-time/chat/vue.md +++ b/docs/guides/real-time/chat/vue.md @@ -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
    -
    + @@ -39,23 +39,15 @@ Create a new Role called `Users`, and give Create and Read access to the `Messag
      - + - +
      - - @@ -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 `
        ` 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 - -
        // [!code --] - // [!code ++] - - - - // [!code --] - // [!code ++] - -``` - -## 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 - - - // [!code --] - // [!code ++] - - // [!code --] - // [!code ++] - -
        -``` - -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 -
        - - // [!code --] - // [!code ++] - -
        +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 `
          ` to display items in the array: +Update your `
            ` to display items in the array by mapping over `messageHistory` -```vue-html +```js
              -
            1. // [!code ++] - {{ message.user_created.first_name }}: {{ message.text }} // [!code ++] -
            2. // [!code ++] +
            3. + {{ message.user_created.first_name }}: {{ message.text }} +
            ``` @@ -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 - -
            -
            - - - - - -
            + +
            +
            + + + + + +
            -
              -
            1. - {{ message.user_created.first_name }}: {{ message.text }} -
            2. -
            +
              +
            1. + {{ message.user_created.first_name }}: {{ message.text }} +
            2. +
            -
            - - - -
            -
            +
            + + + +
            +
            - - - + 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(); + }; + + ``` diff --git a/docs/guides/real-time/getting-started/websockets-js.md b/docs/guides/real-time/getting-started/websockets-js.md new file mode 100644 index 0000000000..add55dcd36 --- /dev/null +++ b/docs/guides/real-time/getting-started/websockets-js.md @@ -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 + + + + + + +``` + +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 ` + + +``` diff --git a/docs/guides/real-time/getting-started/websockets.md b/docs/guides/real-time/getting-started/websockets.md index 7fd5b5f5cf..f3974542e7 100644 --- a/docs/guides/real-time/getting-started/websockets.md +++ b/docs/guides/real-time/getting-started/websockets.md @@ -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 ` + + ``` -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 ` - + 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' }, + }); + } + + ``` diff --git a/docs/guides/real-time/live-poll.md b/docs/guides/real-time/live-poll.md index 9293c809a0..5eb57e7a9c 100644 --- a/docs/guides/real-time/live-poll.md +++ b/docs/guides/real-time/live-poll.md @@ -78,40 +78,44 @@ Also create a `results.html` file and open it in your editor. Add the following: ```html - -
            - -
            - - + - + if (data.type == 'subscription' && data.event == 'init') { + } + + if (data.type == 'subscription' && data.event == 'create') { + } + }); + + ``` @@ -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 - -
            - -
            - - + - + const ctx = document.getElementById('chart'); + + const chart = new Chart(ctx, { + type: 'pie', + data: { + labels: [], + datasets: [ + { + label: '# of Votes', + data: [], + backgroundColor: ['#4f46e5', '#f472b6'], + }, + ], + }, + }); + + ```