diff --git a/assets/images/signaling/authentication-workflow.puml b/assets/images/signaling/authentication-workflow.puml
new file mode 100644
index 000000000..890eb03fd
--- /dev/null
+++ b/assets/images/signaling/authentication-workflow.puml
@@ -0,0 +1,47 @@
+@startuml
+
+!include ../video-sdk/agora_skin.iuml
+
+
+box "Implemented by you"
+
+participant "Token server" as TS
+
+participant "Your app" as APP
+
+end box
+
+
+box "Agora"
+participant "Signaling" as API
+end box
+
+group Log in to Signaling using authentication
+TS -> TS: Configure your token server using\n your App Id and App Certificate
+APP -> TS: Request an RTM token using a user Id
+TS -> TS: Validate request against internal security\n and generate a token
+TS -> APP: Return RTM token to App
+APP -> APP: Initiate the Signaling Engine using\n the App Id
+APP -> API: Log in to Signaling using\n the user Id and the RTM token
+API -> API: Validate the token
+API -> APP: Log in user, then trigger callback
+end
+
+group Join a stream channel
+APP -> TS: Request an RTC token using\n a user Id, and a stream channel name
+TS -> TS: Validate request against internal security\n and generate token
+TS -> APP: Return RTC token to App
+APP -> API: Join a stream channel using\n channel name, user Id, and the RTC token
+end
+
+group Renew token
+API -> APP: Trigger event:\n token privilege will expire
+APP -> TS: Request an RTM token using\n the user ID
+TS -> TS: Validate request against internal security\n and generate a token
+TS -> APP: Return RTM token to App
+API <- APP: Send RTM token to Signaling with a \ncall to renew token
+API -> API: Validate the token
+API -> APP: Trigger callback
+end
+
+@enduml
\ No newline at end of file
diff --git a/assets/images/signaling/authentication-workflow.svg b/assets/images/signaling/authentication-workflow.svg
new file mode 100644
index 000000000..efa0ea6f0
--- /dev/null
+++ b/assets/images/signaling/authentication-workflow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/signaling/cloud-proxy.puml b/assets/images/signaling/cloud-proxy.puml
new file mode 100644
index 000000000..745b1a505
--- /dev/null
+++ b/assets/images/signaling/cloud-proxy.puml
@@ -0,0 +1,45 @@
+@startuml cloud-proxy
+!include ../others/agora_skin.iuml
+
+actor "User" as USR
+
+box "Implemented by you"
+actor "Admin" as ADMIN
+participant "App" as APP
+participant "Enterprise Firewall" as FIREWALL
+end box
+
+box "Provided by Agora"
+participant "Cloud Proxy" as PROXY
+participant "SD-RTN" as API
+end box
+
+ADMIN -> FIREWALL: Whitelist IP addresses and ports\n for Cloud Proxy in the firewall.
+
+USR -> APP: Open the app
+APP -> APP: Initialize the Signaling Engine
+
+group Enable cloud proxy
+APP -> APP: Call the method to enable\na Cloud Proxy connection
+APP -> FIREWALL: Request access to \n Cloud Proxy
+FIREWALL-> FIREWALL : Check whitelist to grant\n access
+FIREWALL -> PROXY: Request access to \n Cloud Proxy
+PROXY -> APP: Proxy information
+end
+
+USR -> APP: Join a channel
+
+APP -> PROXY: Join a channel
+PROXY -> API: Ask to join a channel
+API -> PROXY: Join success
+PROXY -> APP: Join success
+APP -> PROXY: Send data stream
+PROXY <-> API: Send and receive data stream
+PROXY -> APP: Receive data stream
+
+USR -> APP: Leave a channel
+APP -> PROXY: Leave the channel
+PROXY -> API: Channel leave
+
+
+@enduml
\ No newline at end of file
diff --git a/assets/images/signaling/cloud-proxy.svg b/assets/images/signaling/cloud-proxy.svg
new file mode 100644
index 000000000..df6ffca59
--- /dev/null
+++ b/assets/images/signaling/cloud-proxy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/signaling/data-encryption.puml b/assets/images/signaling/data-encryption.puml
new file mode 100644
index 000000000..e590cbe34
--- /dev/null
+++ b/assets/images/signaling/data-encryption.puml
@@ -0,0 +1,33 @@
+@startuml media-stream-encryption
+!include ../others/agora_skin.iuml
+
+actor "User" as USR
+
+box "Implemented by you"
+participant "Authentication system" as SVR
+participant "App" as APP
+end box
+
+box "Provided by Agora"
+participant "SD-RTN" as API
+end box
+
+
+USR -> APP: Start the app
+
+group Setup media stream encryption
+APP -> SVR: Login to the \nauthentication system
+APP <-> SVR: Retrieve a 32-byte key
+APP <-> SVR: Retrieve a 32-byte salt in \nBase64 format
+APP -> APP: Create a encryption configuration using \nthe key and salt
+APP -> API: Set the encryption configuration
+end
+APP -> APP: Initiate the Signaling Engine
+
+USR -> APP: Select a channel to join
+APP <-> SVR: Retrieve an access token.
+APP <-> API: Join a channel
+APP <-> API: Communicate over an\n encrypted data stream
+
+
+@enduml
\ No newline at end of file
diff --git a/assets/images/signaling/data-encryption.svg b/assets/images/signaling/data-encryption.svg
new file mode 100644
index 000000000..5d2deb717
--- /dev/null
+++ b/assets/images/signaling/data-encryption.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/signaling/get-started-workflow.puml b/assets/images/signaling/get-started-workflow.puml
new file mode 100644
index 000000000..7fa3d1154
--- /dev/null
+++ b/assets/images/signaling/get-started-workflow.puml
@@ -0,0 +1,38 @@
+@startuml
+
+!include ../video-sdk/agora_skin.iuml
+
+actor "User" as USR
+
+box "Your app"
+participant "Signaling SDK" as APP
+end box
+
+box "Agora"
+participant "Signaling" as API
+end box
+
+group Initialize
+USR -> APP: Open app
+APP -> APP: Create a signalingEngine instance
+APP -> APP: Add event listeners
+APP -> APP: Set the authentication token
+APP -> API: Log in to Signaling
+end
+
+group Messages
+USR -> APP: Subscribe to a channel
+APP -> API: signalingEngine.subscribe
+USR -> APP: Publish message
+APP -> API: signalingEngine.publish
+APP -> APP: Listen for message events
+API -> APP: message
+APP -> USR: Receive message
+end
+
+group Close
+USR -> APP: Log out
+APP -> API: Log out of Signaling
+end
+
+@enduml
\ No newline at end of file
diff --git a/assets/images/signaling/get-started-workflow.svg b/assets/images/signaling/get-started-workflow.svg
new file mode 100644
index 000000000..67c35e610
--- /dev/null
+++ b/assets/images/signaling/get-started-workflow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/signaling/presence-workflow.puml b/assets/images/signaling/presence-workflow.puml
new file mode 100644
index 000000000..826225bc9
--- /dev/null
+++ b/assets/images/signaling/presence-workflow.puml
@@ -0,0 +1,50 @@
+@startuml
+
+!include ../video-sdk/agora_skin.iuml
+
+actor "User" as USR
+
+box "Your app"
+participant "Signaling SDK" as APP
+end box
+
+box "Agora"
+participant "Signaling" as API
+end box
+
+group Initialize
+USR -> APP: Open app
+APP -> APP: Create a signalingEngine instance
+APP -> APP: Add the presence event listener
+APP -> APP: Set the authentication token
+APP -> API: Log in to Signaling
+end
+
+group Presence notifications
+USR -> APP: Join a channel
+APP -> API: Subscribe to a channel \nand enable presence events for the channel
+API -> APP: Presence event:\n A remote user joins, leaves \nor changes status in the channel
+APP -> APP: Read the event arguments
+APP -> USR: Inform users
+end
+
+group Presence queries
+USR -> APP: Load the list of users in the channel
+APP <-> API: Call getOnlineUsers
+USR -> APP: Load the channel list for a user
+APP <-> API: Call getUserChannels
+end
+
+group User status
+USR -> APP: Set own status
+APP -> API: Call setState
+USR -> APP: Query the status of a remote user
+APP <-> API: Call getState
+end
+
+group Close
+USR -> APP: Log out
+APP -> API: Log out of Signaling
+end
+
+@enduml
\ No newline at end of file
diff --git a/assets/images/signaling/presence-workflow.svg b/assets/images/signaling/presence-workflow.svg
new file mode 100644
index 000000000..18d5029f2
--- /dev/null
+++ b/assets/images/signaling/presence-workflow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/signaling/signaling-metadata-workflow.puml b/assets/images/signaling/signaling-metadata-workflow.puml
new file mode 100644
index 000000000..7dac57b47
--- /dev/null
+++ b/assets/images/signaling/signaling-metadata-workflow.puml
@@ -0,0 +1,56 @@
+@startuml
+
+!include ../video-sdk/agora_skin.iuml
+
+actor "User" as USR
+
+box "Your app"
+participant "Signaling SDK" as APP
+end box
+
+box "Agora"
+participant "Signaling" as API
+end box
+
+group Initialize
+USR -> APP: Open app
+APP -> APP: Create a Signaling client
+APP -> APP: Add client event callbacks
+APP -> APP: Create a Signaling channel
+APP -> APP: Add channel event callbacks
+APP -> API: Log in to Signaling
+APP -> API: Join a channel
+end
+
+group Read and write user metadata
+APP -> API: Set local user metadata
+USR -> APP: Change user information
+APP -> API: Add, update, or delete local user metadata
+APP -> API: Subscribe to metadata of remote users
+APP <- API: On user metadata updated callback
+APP <- API: Retrieve metadata of a remote user
+end
+
+group Read and write channel metadata
+APP -> API: Set channel metadata
+USR -> APP: Update channel information
+APP -> API: Add, update, or delete channel metadata
+APP <- API: On channel metadata updated callback
+APP <- API: Retrieve channel metadata
+end
+
+group Use locks
+APP -> API: Set a lock
+USR -> APP: Update channel metadata with locking
+APP -> API: Acquire the lock
+APP -> API: Specify the lock name when updating metadata
+end
+
+group Close
+USR -> APP: Leave the channel
+APP -> API: Release locks
+APP -> API: Unsubscribe from user metadata update notifications
+APP -> API: Log out of Signaling
+end
+
+@enduml
\ No newline at end of file
diff --git a/assets/images/signaling/signaling-metadata-workflow.svg b/assets/images/signaling/signaling-metadata-workflow.svg
new file mode 100644
index 000000000..ff5f3c62d
--- /dev/null
+++ b/assets/images/signaling/signaling-metadata-workflow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/signaling/signaling-pricing-plans.png b/assets/images/signaling/signaling-pricing-plans.png
new file mode 100644
index 000000000..fc398b5f9
Binary files /dev/null and b/assets/images/signaling/signaling-pricing-plans.png differ
diff --git a/assets/images/signaling/signaling-quickstart-android.png b/assets/images/signaling/signaling-quickstart-android.png
new file mode 100644
index 000000000..470563c02
Binary files /dev/null and b/assets/images/signaling/signaling-quickstart-android.png differ
diff --git a/assets/images/signaling/stream-channel-workflow.puml b/assets/images/signaling/stream-channel-workflow.puml
new file mode 100644
index 000000000..b24108c38
--- /dev/null
+++ b/assets/images/signaling/stream-channel-workflow.puml
@@ -0,0 +1,65 @@
+@startuml
+
+!include ../video-sdk/agora_skin.iuml
+
+actor "User" as USR
+
+box "Your app"
+participant "Signaling SDK" as APP
+end box
+
+box "Agora"
+participant "Signaling" as API
+end box
+
+group Initialize
+USR -> APP: Open app
+APP -> APP: Create a signalingEngine instance
+APP -> APP: Add user event callbacks
+APP -> APP: Set the authentication token
+APP -> API: Log in to Signaling
+end
+
+group Channel
+
+USR -> APP: Create stream channel
+APP <-> API: Create channel or add user to channel
+USR -> APP: Subscribe to a channel
+API <-> APP: Channel events
+
+group Topic
+USR -> APP: Join topic
+APP <-> API: Create topic or add user to topic
+USR -> APP: Subscribe to topic
+API <-> APP: Topic events
+group Messages
+
+USR -> APP: Send message
+APP -> API: Publish message to topic
+
+API -> APP: Message from topic
+APP -> APP: Listen for message events
+APP -> USR: Receive message
+end
+USR -> APP: Leave topic
+APP <-> API: Leave topic
+
+end
+USR -> APP: Leave channel
+APP <-> API: Leave channel
+end
+
+group Presence
+
+USR -> APP: Change status
+APP -> APP: listen for user events
+API -> APP: ConnectionStateChanged
+APP -> USR: Inform users
+
+end
+group Close
+USR -> APP: Log out
+APP -> API: Log out of Signaling
+end
+
+@enduml
\ No newline at end of file
diff --git a/assets/images/signaling/stream-channel-workflow.svg b/assets/images/signaling/stream-channel-workflow.svg
new file mode 100644
index 000000000..f157a924b
--- /dev/null
+++ b/assets/images/signaling/stream-channel-workflow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/broadcast-streaming/reference/cloud-proxy-allowed-ips.mdx b/broadcast-streaming/reference/cloud-proxy-allowed-ips.mdx
index 8e1ed1a0e..2929b5b64 100644
--- a/broadcast-streaming/reference/cloud-proxy-allowed-ips.mdx
+++ b/broadcast-streaming/reference/cloud-proxy-allowed-ips.mdx
@@ -7,7 +7,7 @@ description: >
---
-import Proxy from '../../shared/video-sdk/_cloud-proxy-allowed-iplist.mdx';
+import Proxy from '../../shared/common/_cloud-proxy-allowed-iplist.mdx';
export const toc = [{}];
diff --git a/flexible-classroom/reference/ui-scene.mdx b/flexible-classroom/reference/ui-scene.mdx
index 43fc00ff6..fe5696ac3 100644
--- a/flexible-classroom/reference/ui-scene.mdx
+++ b/flexible-classroom/reference/ui-scene.mdx
@@ -1,5 +1,5 @@
---
-title: 'UI Scene SDK'
+title: 'FcrUIScene SDK'
sidebar_position: 5
type: docs
description: >
diff --git a/interactive-live-streaming/reference/cloud-proxy-allowed-ips.mdx b/interactive-live-streaming/reference/cloud-proxy-allowed-ips.mdx
index 8e1ed1a0e..2929b5b64 100644
--- a/interactive-live-streaming/reference/cloud-proxy-allowed-ips.mdx
+++ b/interactive-live-streaming/reference/cloud-proxy-allowed-ips.mdx
@@ -7,7 +7,7 @@ description: >
---
-import Proxy from '../../shared/video-sdk/_cloud-proxy-allowed-iplist.mdx';
+import Proxy from '../../shared/common/_cloud-proxy-allowed-iplist.mdx';
export const toc = [{}];
diff --git a/iot/reference/cloud-proxy-allowed-ips.mdx b/iot/reference/cloud-proxy-allowed-ips.mdx
index 5fde83e45..2ce375a2f 100644
--- a/iot/reference/cloud-proxy-allowed-ips.mdx
+++ b/iot/reference/cloud-proxy-allowed-ips.mdx
@@ -7,7 +7,7 @@ description: >
---
-import Proxy from '../../shared/video-sdk/_cloud-proxy-allowed-iplist.mdx';
+import Proxy from '../../shared/common/_cloud-proxy-allowed-iplist.mdx';
export const toc = [{}];
diff --git a/shared/video-sdk/_cloud-proxy-allowed-iplist.mdx b/shared/common/_cloud-proxy-allowed-iplist.mdx
similarity index 98%
rename from shared/video-sdk/_cloud-proxy-allowed-iplist.mdx
rename to shared/common/_cloud-proxy-allowed-iplist.mdx
index 1795bf757..f25204eee 100644
--- a/shared/video-sdk/_cloud-proxy-allowed-iplist.mdx
+++ b/shared/common/_cloud-proxy-allowed-iplist.mdx
@@ -1,5 +1,5 @@
-Agora deploys dedicated servers to run the cloud proxy service. Agora owns the IP addresses and fully controls the traffic on these addresses. To use Agora Cloud Proxy Force UDP or Force TCP modes, your end users must first configure their firewalls to trust the following IP address and port ranges.
+Agora deploys dedicated servers to run the cloud proxy service. Agora owns the IP addresses and fully controls the traffic on these addresses. To use Agora Cloud Proxy, your end users must first configure their firewalls to trust the following IP address and port ranges.
When adding IP addresses:
If you use cloud proxy with [network geofencing](../enable-features/geofencing), you can add only the IP addresses dedicated to your specified region.
If you do not enable network geofencing, you need to add all IP addresses to ensure connectivity.
diff --git a/shared/common/_core-concepts.mdx b/shared/common/_core-concepts.mdx
index 4c752276c..8944a76b9 100644
--- a/shared/common/_core-concepts.mdx
+++ b/shared/common/_core-concepts.mdx
@@ -1,16 +1,6 @@
Before you start writing your code, familiarize yourself with the core concepts that SDKs are built on.
-
-##
-
-Agora's core engagement services are powered by its Software-Defined Real-time Network (SD-RTN™) that is accessible and available anytime, anywhere around the world. The software-defined network isn’t confined by device, phone numbers, or a telecommunication provider’s coverage area like traditional networks. has data centers globally that cover over 200+ countries and regions. The network delivers sub-second latency and high availability of real-time video and audio anywhere on the globe. With , Agora can deliver live user engagement experiences in the form of real-time communication (RTC) with the following advantages:
-
-* Unmatched quality of service
-* High availability and accessibility
-* True scalability
-* Low Cost
-
## is the main dashboard where you manage your projects and services. provides an intuitive interface for developers to query and manage their account. After registering an Agora Account, you use the console to perform the following tasks:
@@ -25,31 +15,38 @@ Agora's core engagement services are powered by its Software-Defined Real-time N
also provides RESTful APIs that you use to implement features such as creating a project and fetching usage numbers programmatically.
+
+##
+
+Agora's core engagement services are powered by its Software-Defined Real-time Network (SD-RTN™) that is accessible and available anytime, anywhere around the world. The software-defined network isn’t confined by device, phone numbers, or a telecommunication provider’s coverage area like traditional networks. has data centers globally that cover over 200+ countries and regions. The network delivers sub-second latency and high availability of real-time video and audio anywhere on the globe. With , Agora can deliver live user engagement experiences in the form of real-time communication (RTC) with the following advantages:
+
+* Unmatched quality of service
+* High availability and accessibility
+* True scalability
+* Low Cost
+
+## App
+
+The software you create that integrates functionality. One instance only of your
+may be installed on a device at a time.
+
## App ID
The App ID is a random string generated within when you create a new project. You can create multiple projects in your account; each project has a different App ID. This App ID enables your app users to communicate securely with each other. When you initialize in your app, you pass the App ID as an argument. The App ID is also used to create the authentication tokens that ensure secure communication in a channel. You retrieve your App ID using .
uses this App ID to identify each app, provide billing and other statistical data services.
-
-
-
-
-
-
+
+
For applications requiring high security in a production environment, you must choose a App ID + Token mechanism for [user authentication](../develop/authentication-workflow) when creating a new project. Without an authentication token, your environment is open to anyone with access to your App ID.
-
-
-
-
-
For applications requiring high security in a production environment, you must choose a App ID + Token mechanism for user authentication when creating a new project. Without an authentication token, your environment is open to anyone with access to your App ID.
-
+
For applications requiring high security in a production environment, you must choose a App ID + Token mechanism for [user authentication](../get-started/authentication-workflow) when creating a new project. Without an authentication token, your environment is open to anyone with access to your App ID.
@@ -57,47 +54,18 @@ For applications requiring high security in a production environment, you must c
An App certificate is a string generated by to enable token authentication. It is required for generating an or authentication token. To get your app certificate from , see [Manage app certificates](../reference/manage-agora-account#get-the-app-certificate).
-
-
-
-
-
-
+
To use your App certificate for setting up a token server, see [Create and run a token server.](../develop/authentication-workflow#create-and-run-a-token-server)
-
-
-
-
-
-
+
To use your App certificate for setting up a token server, see [Create and run a token server.](../get-started/authentication-workflow#create-and-run-a-token-server)
-## Token
-
-A token is a dynamic key that is used by the authentication server to check user permissions. You use to generate a temporary token for testing purposes during the development process. In a production environment, you implement a token server in your security infrastructure to control access to your channels.
-
-
-
-
-
-
-For more information, see [Secure authentication with tokens](../develop/authentication-workflow).
-
-
-
-
-
-
-
-
-For more information, see [Secure authentication with tokens](../get-started/authentication-workflow).
-
-
## Channel
+ uses the *channel name* to identify a channel. Users who specify the same *channel name* join a common channel and interact with each other. A channel is created when the first user joins. It ceases to exist when the last user leaves.
You create a channel by calling the methods for transmitting real-time data. uses different channels to transmit different types of data. The channel transmits audio or video data, while the channel transmits messaging or signaling data. The and channels are independent of each other.
@@ -113,10 +81,27 @@ The SDK applies different optimization methods according to the selected channel
| COMMUNICATION | This profile is suitable for one-on-one or group calls, where all users in the channel talk freely. |
| LIVE\_BROADCASTING | In a live streaming channel, users have two client roles: *host* and *audience*. The *host* sends and receives audio or video, while the *audience* only receives audio or video with the sending function disabled. |
-## User ID
+
+
+
-A user ID (UID) identifies a user in a channel. Each user in a channel should have a unique user ID. If you do not specify a user ID when the user joins a channel, a UID is automatically generated and assigned to the user.
+A data transfer management mechanism for passing data from one device to another. Any user who subscribes to or joins a channel can receive messages or events transmitted in that channel. Clients can subscribe to or join multiple channels at the same time.
+ includes the following types of channels:
+
+| Channel type | Main features | Applicable scenario|
+|---------------|---------------|--------------------|
+| Message | Follows the industry-standard pub/sub model. Channels do not need to be created in advance, and there is no upper limit on the number of publishers and subscribers in a channel.|Multi-device management and command exchange in the IoT industry, location tracking in smart devices, etc.|
+| Stream |Follows the chat room model. Users need to join the channel to send and receive event notifications. Messages are managed and delivered through topics, and a single channel allows up to 1000 users to join at the same time. Supports channel sharing and synchronous transmission of audio and video data.|High-frequency and large concurrent data transmission or co-channel and synchronous transmission with audio and video data, such as metaverse, cloud games, etc.|
+
+
+
+## Device
+
+A physical or virtual device that connects to . For example, a mobile device, computer,
+smart watch, or IoT device.
+
+
## Stream
A stream is a sequence of digitally-encoded coherent signals that contains audio or video data. Users in a channel [publish](#publish) local streams and [subscribe](#subscribe) to remote streams from other users.
@@ -131,6 +116,41 @@ After successfully publishing a stream, the SDK continues sending media data to
Subscribing is the act of receiving media streams published by remote users to the channel. A user receives audio and video data from other users by subscribing to one or more of their streams. You either directly play the subscribed streams or process incoming data for other purposes such as recording or capturing screenshots.
For further details, on how to create, manage and update your account, see [Manage your account](../reference/manage-agora-account).
+
+
+## Token
+
+A token is a dynamic key that is used by the authentication server to check user permissions. You use to generate a temporary token for testing purposes during the development process. In a production environment, you implement a token server in your security infrastructure to control access to your channels.
+
+
+
+For more information, see [Secure authentication with tokens](../get-started/authentication-workflow).
+
+
+
+For more information, see [Secure authentication with tokens](../get-started/authentication-workflow).
+
+
+
+## User ID
+
+
+A User ID (UID) identifies a user in a channel. Each user in a channel should have a unique user ID. If you do not
+ specify a user ID when the user joins a channel, a UID is automatically generated and assigned to the user.
+
+
+
+A user ID (UID) identifies a user in a channel. A user is a person or entity that logs into your .
+Each user in a [project](#app-id) must have a globally unique UID.
+
+The same UID cannot log in to from multiple devices at the same time. If s with the same UID logs in to , the that logged in first
+is disconnected and sent an event notification.
+
+The UID is used for billing and online status notifications.
+
+
diff --git a/shared/common/_create-run-token-server.mdx b/shared/common/_create-run-token-server.mdx
new file mode 100644
index 000000000..22b8faff9
--- /dev/null
+++ b/shared/common/_create-run-token-server.mdx
@@ -0,0 +1,65 @@
+This section shows you how to deploy a token server on a cloud platform.
+
+1. Start deploying the token server to your cloud platform:
+ The cloud platform retrieves the project code and necessary files from Github, then takes you to the
+ **Deployment** page.
+ - [Render](https://render.com/deploy?repo=https://github.com/AgoraIO-Community/agora-token-service)
+ - [Railway](https://railway.app/new/template/NKYzQA?referralCode=waRWUT)
+ - [Heroku](https://www.heroku.com/deploy/?template=https://github.com/AgoraIO-Community/agora-token-service)
+
+1. Fill in the information needed by your cloud platform:
+
+ 1. `Blueprint name`: A unique name for your deployment.
+
+ 1. `Branch name`: The branch of the repo or fork you want to deploy, default is main.
+
+ 1. `APP_CERTIFICATE`: The App Certificate obtained from .
+
+ 1. `APP_ID`: The [App ID](../reference/manage-agora-account#get-the-app-id) obtained from .
+
+ 1. `Github account`: The GitHub account where the cloud platform should clone the token server repository.
+
+ 1. `Repository name`: The name of the cloned repository, the default is `agora-token-service`.
+
+ 1. `Private repository`: Select this option to hide this repository.
+
+2. Click **Deploy**. The platform configures and builds the token server.
+
+3. Click the **URL**.
+
+ You are notified of a URL where your server is deployed. Click the link and open the token server in your
+ browser. Don’t worry if you see `404 page not found` in your browser. Follow the next steps and test your server.
+
+4. Test your server
+
+ To retrieve a token, send a request to the token server using a URL based on the [Token server GET request structure](#token-server-get-request-structure):
+
+
+ ```text
+ /rtc/:channelName/:role/:tokentype/:uid/?expiry=expireTime
+ ```
+
+ For example: `https://agora-token-service-production-1234.up.railway.app/rtc/MyChannel/1/uid/1/?expiry=300`
+
+
+
+ ```text
+ /rtm/:uid/?expiry=expireTime
+ ```
+
+ For example: `https://agora-token-service-production-1234.up.railway.app/rtm/bob/?expiry=600`
+
+
+ Your token server returns a JSON object containing an encrypted token:
+
+
+ ```json
+ {"rtcToken":"ThisIsAnExampleTokenThisIsAnExampleTokenThisIsAnExampleTokenThisIsAnExampleTokenThisIsAnExampleToken"}
+ ```
+
+
+ ```json
+ {"rtmToken":"ThisIsAnExampleTokenThisIsAnExampleTokenThisIsAnExampleTokenThisIsAnExampleTokenThisIsAnExampleToken"}
+ ```
+
+
diff --git a/shared/common/_manage-agora-account.mdx b/shared/common/_manage-agora-account.mdx
index 3aa4cab28..601a3a70c 100644
--- a/shared/common/_manage-agora-account.mdx
+++ b/shared/common/_manage-agora-account.mdx
@@ -10,7 +10,7 @@ import OnlinePayment from '@docs/shared/common/manage-agora-account/_online-paym
import Ticket from '@docs/shared/common/manage-agora-account/_ticket.mdx';
import DeleteAccount from '@docs/shared/common/manage-agora-account/_delete-account.mdx';
-
+This page shows you how to use to manage all aspects of your account.
diff --git a/shared/common/_supported-platforms.mdx b/shared/common/_supported-platforms.mdx
index 364d401ea..630866ef4 100644
--- a/shared/common/_supported-platforms.mdx
+++ b/shared/common/_supported-platforms.mdx
@@ -1,7 +1,8 @@
-import * as data from '@site/data/variables'; import {ProductWrapper} from "../../../src/mdx-components/ProductWrapper";
+import * as data from '@site/data/variables';
This section lists the platforms and products you use to develop apps that interact with .
+
- *Android*:
@@ -10,7 +11,7 @@ This section lists the platforms and products you use to develop apps that inter
| The latest version of Chrome | Supported | Supported |
| The latest version of WeChat | Supported | Supported |
- *iOS*:
- |Operating system |Features |VP9 | H.264|
+ |Operating system |Features |VP8 | H.264|
|---|---|---|---|
|Applications with built-in WebView|Receiving streams|iOS 12.2 and later |iOS 12.1.4 and later|
|Applications with built-in WebView| Sending streams|iOS 14.3 and later |iOS 14.3 and later|
@@ -22,6 +23,7 @@ This section lists the platforms and products you use to develop apps that inter
|Windows|
Chrome
Firefox
Edge
|Supported |Supported|
|ChromeOS |Chrome |Supported |Supported|
+
To ensure the best user experience, best practice is to use the latest version each browser on the latest version of the operating system. Download the latest version of:
- [Chrome](https://www.google.com/intl/en/chrome/)
@@ -29,7 +31,6 @@ To ensure the best user experience, best practice is to use the latest version e
- [Edge](https://microsoft.com/edge)
- [Safari](https://support.apple.com/en-hk/HT201541)
-
@@ -148,6 +149,7 @@ To ensure the best user experience, best practice is to use the latest version e
| Flutter | Flutter 1.0.0 or later |
+
@@ -162,6 +164,7 @@ To ensure the best user experience, best practice is to use the latest version e
+
- *Android*
| Browser or application | Receiving streams | Sending streams |
|-----------------|--------------------------|---------------------------------------|
@@ -187,3 +190,74 @@ To ensure the best user experience, best practice is to use the latest version e
- [Edge](https://microsoft.com/edge)
- [Safari](https://support.apple.com/en-hk/HT201541)
+
+
+
+### Supported browsers
+
+
+ supports the following browsers:
+
+- *Android*
+ | Browser or application | Minimum supported version |
+ |----------------------------------|---------------------------|
+ | Chrome | 86.0.4240 |
+ | Firefox (x64, ARM64, IA-32, ARMv7| 83.0 |
+
+- *iOS*:
+ | Browser or application | Minimum supported version |
+ |------------------------|---------------------------|
+ | Chrome | 86.0.4240 |
+ | Firefox | 29.0 |
+
+- *Desktop*:
+ |Operating system |Browser |Minimum supported version |
+ |-----------------|--------------|--------------------------|
+ |Windows |Firefox |83.0 |
+ |Windows |Chrome |86.0.4240 |
+ |Windows |Microsoft Edge|87.0.664.60 |
+ |macOS |Chrome |86.0.4240 |
+ |macOS |Firefox |83.0 |
+ |macOS |Safari |13.0 |
+ |Linux |Firefox |83.0 |
+ |Linux |Chrome |86.0.4240 |
+ |OpenBSD |Firefox |83.0 |
+
+### Dependencies
+
+When you integrate features into your , has the following
+ dependencies:
+
+| Name | Minimum version | License | Dependency |
+|---|---|---|---|
+| @thi.ng/cache | 1.0.70 | Apache 2.0 | Transitive |
+| @types/format-util | 1.0.1 | MIT | Transitive |
+| @types/google-closure-compiler | 0.0.18 | MIT | Transitive |
+| @types/node-forge | 1.3.1 | BSD 2-Clause | Transitive |
+| abortcontroller-polyfill | 1.7.1 | MIT | Transitive |
+| console-polyfill | 0.3.0 | MIT | Transitive |
+| core-js | 3.9.0 | MIT | Transitive |
+| debug | 4.3.4 | MIT | Transitive |
+| dotenv | 8.2.0 | BSD 2-Clause | Transitive |
+| events | 3.2.0 | MIT | Transitive |
+| format-util | 1.0.5 | MIT | Transitive |
+| gud | 1.0.0 | MIT | Transitive |
+| is-mobile | 3.1.1 | MIT | Transitive |
+| is-what | 3.14.1 | MIT | Transitive |
+| lodash | 4.17.21 | MIT | Transitive |
+| long | 4.0.0 | Apache 2.0 | Transitive |
+| lru-cache | 6.0.0 | ISC | Transitive |
+| never throw | 5.0.1 | MIT | Transitive |
+| node-forge | 1.3.1 | BSD 3-Clause or GPL 2 | Transitive |
+| promise-polyfill | 8.2.0 | MIT | Transitive |
+| protobufjs | 6.10.2 | MIT | Transitive |
+| regenerator-runtime | 0.13.9 | MIT | Transitive |
+| rxjs | 6.6.3 | Apache 2.0 | Transitive |
+| rxjs-etc | 10.6.0 | MIT | Transitive |
+| rxjs-websockets | 8.0.1 | ISC | Transitive |
+| safe-json-stringify | 1.2.0 | MIT | Transitive |
+| tslib | 2.1.0 | BSD Zero Clause | Transitive |
+| uuid | 3.4.0 | MIT | Transitive |
+| webcrypto-shim | 0.1.6 | MIT | Transitive |
+
+
\ No newline at end of file
diff --git a/shared/common/_token-server-source-request-structure.mdx b/shared/common/_token-server-source-request-structure.mdx
new file mode 100644
index 000000000..75221d38c
--- /dev/null
+++ b/shared/common/_token-server-source-request-structure.mdx
@@ -0,0 +1,34 @@
+
+#### Source code for a token server
+
+The token server RESTful web service used in this page is written in Golang using the Gin framework. Want to use the code in your authentication service? [Download](https://github.com/AgoraIO-Community/agora-token-service/releases/tag/v1.1.1) the token server source code and binaries for various platforms from Github.
+
+To see how to create a token generator inside your IAM system, see [Integrate a token generator](../develop/integrate-token-generation).
+
+#### Token server GET request structure
+
+A token server GET request has the following structure:
+
+ ``` html
+ /rtc/:channelName/:role/:tokentype/:uid/?expiry=expireTime
+ ```
+
+- `:channelName` is the name of the Channel you wish to join
+
+ A channel name may contain numbers with both upper and lower case letters. The name length must be less than 64 characters.
+
+- `:role` is the user role
+
+ Use `publisher` for publisher, `subscriber` for subscriber.
+
+- `:tokentype` is the type of token
+
+ supports both integer user IDs and string user accounts for token generation. To ensure smooth communication, all the users in a channel must use the same type of ID, that is, either the integer `uid`, or a string `userAccount`. Best practice is to use the `uid`.
+
+- `:uid` is the user ID
+
+ User Id can be any 32-bit unsigned integer. It can be set to 0, if you do not need to authenticate the user based on the user ID.
+
+- `expireTime` (optional) is the number of seconds after which the token will expire
+
+ By default, a token expires after 24 hours unless a shorter life span is explicitly specified in the token request.
diff --git a/shared/common/prerequities.mdx b/shared/common/prerequities.mdx
index cdbe5d10c..b12c6a24f 100644
--- a/shared/common/prerequities.mdx
+++ b/shared/common/prerequities.mdx
@@ -1,4 +1,5 @@
To test the code used in this page you need to have:
+
* Implemented either of the following:
- [ quickstart](../get-started/get-started-uikit)
@@ -26,9 +27,12 @@ To test the code used in this page you need to have:
| [Windows](https://docs.unrealengine.com/4.27/en-US/Basics/InstallingUnrealEngine/RecommendedSpecifications/) | 32-bit Windows only supports Unreal Engine 4 and below. You need to uncomment the Windows 32-bit related code in the file `AgoraPluginLibrary.Build.cs`. |
+
- A [supported browser](../reference/supported-platforms#browsers).
+
- Physical media input devices, such as a camera and a microphone.
+
- A JavaScript package manager such as [npm](https://www.npmjs.com/package/npm).
diff --git a/shared/common/project-setup/web.mdx b/shared/common/project-setup/web.mdx
index 2a69bf074..8b6968fec 100644
--- a/shared/common/project-setup/web.mdx
+++ b/shared/common/project-setup/web.mdx
@@ -1,19 +1,24 @@
+
1. **Clone the [ reference app](https://github.com/AgoraIO/video-sdk-samples-js) repository**.
Navigate to your `` folder and run the following command:
-
+
```bash
git clone https://github.com/AgoraIO/video-sdk-samples-js.git
```
-
-
+
+
+1. **Clone the [ reference app](https://github.com/AgoraIO/signaling-sdk-samples-web) repository**.
+
+ Navigate to your `` folder and run the following command:
+
```bash
git clone https://github.com/AgoraIO/signaling-sdk-samples-web
```
-
+
1. **Install dependencies**:
diff --git a/shared/common/project-test/index.mdx b/shared/common/project-test/index.mdx
index 70939fe59..b03b6664a 100644
--- a/shared/common/project-test/index.mdx
+++ b/shared/common/project-test/index.mdx
@@ -11,7 +11,9 @@ import Windows from './windows.mdx';
import CloneProj from './clone-project.mdx'
+
+
diff --git a/shared/common/project-test/web.mdx b/shared/common/project-test/web.mdx
index f665e77b0..a021e3e4c 100644
--- a/shared/common/project-test/web.mdx
+++ b/shared/common/project-test/web.mdx
@@ -1,8 +1,14 @@
-
-3. **Set the AppID**
-
- In `src/agora_manager/config.json`, set `appId` to the [AppID](../reference/manage-agora-account?platform=android#get-the-app-id) of your project.
+
+
+1. **Set the AppID**
+
+ In `src/agora_manager/config.json`, set `appId` to the [AppID](../reference/manage-agora-account#get-the-app-id) of your project.
+
+
+ In `/src/signaling_manager/config.json`, set `appId` to the [AppID](../reference/manage-agora-account#get-the-app-id) of your project.
+
+
1. **Set the authentication method**
Choose one of the following authentication methods:
@@ -19,13 +25,13 @@
1. **Run the reference app**
- In Terminal, navigate to \, then run the following command:
+ In Terminal, navigate to ``, then run the following command:
```bash
pnpm dev
```
Use the URL displayed in the terminal to open the in your browser.
-
+
diff --git a/shared/flexible-classroom/customize-ui-scene-sdk/android.mdx b/shared/flexible-classroom/customize-ui-scene-sdk/android.mdx
new file mode 100644
index 000000000..df69ecd25
--- /dev/null
+++ b/shared/flexible-classroom/customize-ui-scene-sdk/android.mdx
@@ -0,0 +1,5 @@
+
+
+Currently not supported for this platform.
+
+
\ No newline at end of file
diff --git a/shared/flexible-classroom/customize-ui-scene-sdk/index.mdx b/shared/flexible-classroom/customize-ui-scene-sdk/index.mdx
index a99bdd2c9..196e24ea1 100644
--- a/shared/flexible-classroom/customize-ui-scene-sdk/index.mdx
+++ b/shared/flexible-classroom/customize-ui-scene-sdk/index.mdx
@@ -1,6 +1,10 @@
import Electron from './electron.mdx'
import Web from './web.mdx';
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+
+
diff --git a/shared/flexible-classroom/customize-ui-scene-sdk/ios.mdx b/shared/flexible-classroom/customize-ui-scene-sdk/ios.mdx
new file mode 100644
index 000000000..66b6cfb38
--- /dev/null
+++ b/shared/flexible-classroom/customize-ui-scene-sdk/ios.mdx
@@ -0,0 +1,5 @@
+
+
+Currently not supported for this platform.
+
+
\ No newline at end of file
diff --git a/shared/flexible-classroom/release-notes/js.mdx b/shared/flexible-classroom/release-notes/js.mdx
index e47acc337..0100c3e4a 100644
--- a/shared/flexible-classroom/release-notes/js.mdx
+++ b/shared/flexible-classroom/release-notes/js.mdx
@@ -7,12 +7,12 @@ v2.9.0 was released on November 22, 2023.
#### New features
-This version adds the following new features:
+This version adds the following new scenarios:
- Online classroom scenarios and the introduction of a new UI style and interactive experience that are closer to the usage habits of educational users. See the following documentation:
- - [Scene SDK](../develop/customize-ui/customize-ui-scene-sdk)
- - [API Reference](ui-scene)
+ - [FcrUIScene SDK](../develop/customize-ui/customize-ui-scene-sdk)
+ - [API Reference](../reference/ui-scene)
One-to-one private chat function. This function does not currently support retrieving historical messages from servers on demand.
diff --git a/shared/signaling/authentication-workflow/implementation/android.mdx b/shared/signaling/authentication-workflow/implementation/android.mdx
new file mode 100644
index 000000000..70f3149de
--- /dev/null
+++ b/shared/signaling/authentication-workflow/implementation/android.mdx
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/implementation/index.mdx b/shared/signaling/authentication-workflow/implementation/index.mdx
new file mode 100644
index 000000000..f25927243
--- /dev/null
+++ b/shared/signaling/authentication-workflow/implementation/index.mdx
@@ -0,0 +1,15 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import ReactNative from './react-native.mdx';
+import Unity from './unity.mdx';
+import Web from './web.mdx';
+import Windows from './windows.mdx';
+
+In , each authentication token you create is specific for a user ID in your . You create a token for each user who logs in to . When you initiate the , ensure that the UID is the same one you used to create the token.
+
+
+
+
+
+
+
diff --git a/shared/signaling/authentication-workflow/implementation/ios.mdx b/shared/signaling/authentication-workflow/implementation/ios.mdx
new file mode 100644
index 000000000..b2418ba5a
--- /dev/null
+++ b/shared/signaling/authentication-workflow/implementation/ios.mdx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/shared/signaling/release-notes/react-native.mdx b/shared/signaling/authentication-workflow/implementation/react-native.mdx
similarity index 60%
rename from shared/signaling/release-notes/react-native.mdx
rename to shared/signaling/authentication-workflow/implementation/react-native.mdx
index dc59b730e..25a8fca94 100644
--- a/shared/signaling/release-notes/react-native.mdx
+++ b/shared/signaling/authentication-workflow/implementation/react-native.mdx
@@ -1,4 +1,3 @@
-
-
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/implementation/unity.mdx b/shared/signaling/authentication-workflow/implementation/unity.mdx
new file mode 100644
index 000000000..7a090839d
--- /dev/null
+++ b/shared/signaling/authentication-workflow/implementation/unity.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/implementation/web.mdx b/shared/signaling/authentication-workflow/implementation/web.mdx
new file mode 100644
index 000000000..3d70cc0bc
--- /dev/null
+++ b/shared/signaling/authentication-workflow/implementation/web.mdx
@@ -0,0 +1,161 @@
+
+
+In order to make HTTPS calls to a token server and interpret the JSON return parameters, the axios HTTP client is integrated into the reference .
+
+To retrieve tokens from the token server and use them to authenticate your with using :
+
+1. **Retrieve an RTM token from the server**
+
+ Use a GET request to retrieve an RTM authentication token for initializing the engine.
+
+ ``` javascript
+ // Get the config
+ const config = signalingManager.config;
+
+ // Fetches the Signaling token
+ async function fetchToken(uid) {
+ if (config.serverUrl !== "") {
+ try {
+ const res = await fetch(
+ config.proxyUrl +
+ config.serverUrl +
+ "/rtm/" +
+ uid +
+ "/?expiry=" +
+ config.tokenExpiryTime,
+ {
+ headers: {
+ "X-Requested-With": "XMLHttpRequest",
+ },
+ }
+ );
+ const data = await res.text();
+ const json = await JSON.parse(data);
+ console.log("RTM token fetched from server: ", json.rtmToken);
+ return json.rtmToken;
+ } catch (err) {
+ console.log(err);
+ }
+ } else {
+ return config.token; // Fallback
+ }
+ }
+ ```
+
+1. **Use the token to login into the **
+
+ Call `fetchToken` to get a fresh RTM token. Use the token in the `RTMConfig` to initiate the .
+
+ ``` javascript
+ const fetchTokenAndLogin = async (uid) => {
+ const token = await fetchToken(uid);
+ signalingManager.login(uid, token);
+ };
+ ```
+1. **Retrieve an RTC token**
+
+ To join a stream channel you retrieve an RTC token from the token server by specifying the `uid` and `channelName`.
+
+ ```javascript
+ // Fetches an RTC token for stream channels
+ async function fetchRTCToken(uid, channelName) {
+ if (config.serverUrl !== "") {
+ try {
+ const res = await fetch(
+ config.proxyUrl +
+ config.serverUrl +
+ "/rtc/" +
+ channelName +
+ "/" +
+ role +
+ "/uid/" +
+ uid +
+ "/?expiry=" +
+ config.tokenExpiryTime,
+ {
+ headers: {
+ "X-Requested-With": "XMLHttpRequest",
+ },
+ }
+ );
+ const data = await res.text();
+ const json = await JSON.parse(data);
+ console.log("RTC token fetched from server: ", json.rtcToken);
+ return json.rtcToken;
+ } catch (err) {
+ console.log(err);
+ }
+ } else {
+ return config.rtcToken;
+ }
+ }
+ ```
+
+1. **Use the RTC token to join a stream channel**
+
+ Create a stream channel using the channel name and call `join` with the RTC token.
+
+ ```javascript
+ const streamChannelJoinAndLeave = async function (
+ isStreamChannelJoined,
+ uid,
+ streamChannelName
+ ) {
+ const token = await fetchRTCToken(uid, streamChannelName);
+ if (getSignalingStreamChannel() === null) {
+ streamChannel = await signalingManager
+ .getSignalingEngine()
+ .createStreamChannel(streamChannelName); // creates a stream channel
+ }
+
+ if (isStreamChannelJoined === false) {
+ await streamChannel
+ .join({
+ token: token,
+ withPresence: true,
+ })
+ .then((response) => {
+ console.log(response);
+ });
+ } else {
+ streamChannel.leave().then((response) => {
+ console.log(response);
+ messageCallback("Left the channel: " + streamChannelName);
+ streamChannel = null;
+ });
+ }
+ };
+ ```
+
+1. **Handle the event triggered by when the token is about to expire**
+
+ A token expires after the `tokenExpiryTime` specified in the call to the token server. If the expiry time
+ is not specified the default timeout is 24 hours. The `TokenPrivilegeWillExpire` event receives a callback when
+ the current token is about to expire so that a fresh token may be retrieved and used.
+
+ ``` javascript
+ // When token-privilege-will-expire occurs, fetch a new token from the server and call renewToken to renew the token.
+ const handleSignalingEvents = (event, eventArgs) => {
+ switch (event) {
+ case "TokenPrivilegeWillExpire":
+ renewToken(uid);
+ break;
+ }
+ }
+ ```
+
+1. **Renew the token**
+
+ You persist the existing session by retrieving a fresh token and calling `renewToken`.
+
+ ``` javascript
+ const renewToken = async (uid) => {
+ const token = await fetchToken(uid);
+ const result = await signalingManager
+ .getSignalingEngine()
+ .renewToken(token);
+ messageCallback("Token was about to expire so it was renewed...");
+ };
+ ```
+
+
diff --git a/shared/signaling/authentication-workflow/implementation/windows.mdx b/shared/signaling/authentication-workflow/implementation/windows.mdx
new file mode 100644
index 000000000..d936cf6f0
--- /dev/null
+++ b/shared/signaling/authentication-workflow/implementation/windows.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/index.mdx b/shared/signaling/authentication-workflow/index.mdx
new file mode 100644
index 000000000..b7c219da9
--- /dev/null
+++ b/shared/signaling/authentication-workflow/index.mdx
@@ -0,0 +1,84 @@
+import Setup from '@docs/shared/common/project-setup/index.mdx';
+import Prerequisites from '@docs/shared/common/prerequities.mdx';
+import CreateRunTokenServer from '@docs/shared/common/_create-run-token-server.mdx';
+import Implement from '@docs/shared/signaling/authentication-workflow/implementation/index.mdx';
+import Test from '@docs/shared/signaling/authentication-workflow/test/index.mdx';
+import Reference from '@docs/shared/signaling/get-started-sdk/reference/index.mdx';
+
+Authentication is the act of validating the identity of each user before they access a system.
+uses digital tokens to authenticate users and their privileges before they access . Each token is valid
+for a limited period and works for a specific UID only. For example, you cannot use a token generated for UID
+`john` for UID `doe`.
+
+This page shows you how to quickly set up an authentication token server, retrieve a token from the server, and use
+it to connect securely to as a specific user. You use this server for development
+purposes. To see how to develop your own token generator and integrate it into your production IAM system, read
+[Token generators](/signaling/develop/integrate-token-generation).
+
+## Understand the tech
+
+An authentication token is a dynamic key that is valid for a maximum of 24 hours. On request, a token server returns an authentication token that is valid for a specific user. A token server generates two types of tokens:
+
+* To initialize the , you use an RTM token. An RTM token is valid only for the user Id that you use to generate it.
+* To join a stream channel, you use an RTC token. An RTC token is valid only for the channel name and the user Id that you use to generate it.
+
+The following figure shows the call flow you need to implement to create step-up-authentication with :
+
+![token authentication flow](/images/signaling/authentication-workflow.svg)
+
+To initiate the engine, your retrieves an RTM token from the token server in your security infrastructure. Your then sends this token to for authentication. validates the token and reads the user and project information stored in the token.
+
+To join a stream channel you request an RTC token from the server by supplying a user Id and a channel name. You do not need an authentication token to subscribe to a message channel.
+
+A token contains the following information:
+
+- The [App ID](../reference/manage-agora-account#get-the-app-id) of your project
+
+- The [App certificate](../reference/manage-agora-account#get-the-app-certificate) of your project
+
+- The User Id of the user to be authenticated
+
+- The Unix timestamp showing when the token will expire
+
+- The channel name (only for RTC tokens)
+
+When the token is about to expire, sends an event to your and you renew the session with a new token.
+
+## Prerequisites
+
+To follow this page, you must have:
+
+- Implemented the [](../get-started/get-started-sdk)
+- Created a cloud platform account that is verified through your GitHub account. The following platforms are
+currently supported:
+ - [Render](https://render.com/)
+ - [Railway](https://railway.app/)
+ - [Heroku](https://www.heroku.com/)
+
+To integrate a token generator directly into your security infrastructure, see [Token generators](/signaling/develop/integrate-token-generation).
+
+## Implement the authentication workflow
+
+In the , the uses an authentication token obtained manually to join a channel. In a production environment, your retrieves this token from a token server. This section shows you how to:
+
+1. [Create and run a token server](#create-and-run-a-token-server)
+2. [Retrieve and use tokens from a token server](#authentication-using-the-sdk)
+
+### Create and run a token server
+
+
+
+### Authenticate your session
+
+
+
+
+## Test authentication
+
+
+
+## Reference
+
+This section contains information that completes the information in this page, or points you to documentation that explains other aspects to this product.
+
+
diff --git a/shared/signaling/authentication-workflow/reference/android.mdx b/shared/signaling/authentication-workflow/reference/android.mdx
new file mode 100644
index 000000000..70f3149de
--- /dev/null
+++ b/shared/signaling/authentication-workflow/reference/android.mdx
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/reference/index.mdx b/shared/signaling/authentication-workflow/reference/index.mdx
new file mode 100644
index 000000000..ee99b112a
--- /dev/null
+++ b/shared/signaling/authentication-workflow/reference/index.mdx
@@ -0,0 +1,20 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import ReactNative from './react-native.mdx';
+import Unity from './unity.mdx';
+import Web from './web.mdx';
+import Windows from './windows.mdx';
+
+- To ensure communication security in a test or production environment, use a token server to generate token is
+recommended to ensure communication security, see [Secure authentication with tokens](../develop/authentication-workflow).
+
+- [Downloads](../reference/downloads)
+
+- SDK Integration Methods:
+
+
+
+
+
+
+
diff --git a/shared/signaling/authentication-workflow/reference/ios.mdx b/shared/signaling/authentication-workflow/reference/ios.mdx
new file mode 100644
index 000000000..b2418ba5a
--- /dev/null
+++ b/shared/signaling/authentication-workflow/reference/ios.mdx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/shared/signaling/authentication-workflow/reference/react-native.mdx b/shared/signaling/authentication-workflow/reference/react-native.mdx
new file mode 100644
index 000000000..25a8fca94
--- /dev/null
+++ b/shared/signaling/authentication-workflow/reference/react-native.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/reference/unity.mdx b/shared/signaling/authentication-workflow/reference/unity.mdx
new file mode 100644
index 000000000..7a090839d
--- /dev/null
+++ b/shared/signaling/authentication-workflow/reference/unity.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/reference/web.mdx b/shared/signaling/authentication-workflow/reference/web.mdx
new file mode 100644
index 000000000..ab0d3723c
--- /dev/null
+++ b/shared/signaling/authentication-workflow/reference/web.mdx
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/reference/windows.mdx b/shared/signaling/authentication-workflow/reference/windows.mdx
new file mode 100644
index 000000000..d936cf6f0
--- /dev/null
+++ b/shared/signaling/authentication-workflow/reference/windows.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/test/android.mdx b/shared/signaling/authentication-workflow/test/android.mdx
new file mode 100644
index 000000000..70f3149de
--- /dev/null
+++ b/shared/signaling/authentication-workflow/test/android.mdx
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/test/index.mdx b/shared/signaling/authentication-workflow/test/index.mdx
new file mode 100644
index 000000000..41d09ec7e
--- /dev/null
+++ b/shared/signaling/authentication-workflow/test/index.mdx
@@ -0,0 +1,15 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import ReactNative from './react-native.mdx';
+import Unity from './unity.mdx';
+import Web from './web.mdx';
+import Windows from './windows.mdx';
+
+To test authentication using a token server:
+
+
+
+
+
+
+
diff --git a/shared/signaling/authentication-workflow/test/ios.mdx b/shared/signaling/authentication-workflow/test/ios.mdx
new file mode 100644
index 000000000..b2418ba5a
--- /dev/null
+++ b/shared/signaling/authentication-workflow/test/ios.mdx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/shared/signaling/authentication-workflow/test/react-native.mdx b/shared/signaling/authentication-workflow/test/react-native.mdx
new file mode 100644
index 000000000..25a8fca94
--- /dev/null
+++ b/shared/signaling/authentication-workflow/test/react-native.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/test/unity.mdx b/shared/signaling/authentication-workflow/test/unity.mdx
new file mode 100644
index 000000000..7a090839d
--- /dev/null
+++ b/shared/signaling/authentication-workflow/test/unity.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/authentication-workflow/test/web.mdx b/shared/signaling/authentication-workflow/test/web.mdx
new file mode 100644
index 000000000..e53740d8a
--- /dev/null
+++ b/shared/signaling/authentication-workflow/test/web.mdx
@@ -0,0 +1,34 @@
+
+
+1. **Configure your project**
+
+ In `/src/signaling_manager/config.json`, Replace:
+ - `appId` - with the value from .
+ - `serverUrl` - with the base URL for your token server. For example: `https://agora-token-service-production-yay.up.railway.app`.
+
+
+1. **Run the reference app**:
+
+ In Terminal, navigate to ``, then run the following command:
+
+ ``` bash
+ pnpm dev
+ ```
+
+1. **Test the authentication server functionality**:
+
+ For each user you want to communicate between:
+
+ 1. Use the URL displayed in Terminal to open the app in your browser.
+
+ 1. Enter a numeric user ID.
+
+ 1. Press **Login**.
+
+ 1. Enter a channel name.
+
+ 1. Press **Join** to join a stream channel.
+
+ 1. Send messages between users.
+
+
diff --git a/shared/signaling/authentication-workflow/test/windows.mdx b/shared/signaling/authentication-workflow/test/windows.mdx
new file mode 100644
index 000000000..d936cf6f0
--- /dev/null
+++ b/shared/signaling/authentication-workflow/test/windows.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/_cloud-proxy.mdx b/shared/signaling/cloud-proxy/_cloud-proxy.mdx
new file mode 100644
index 000000000..c2f918486
--- /dev/null
+++ b/shared/signaling/cloud-proxy/_cloud-proxy.mdx
@@ -0,0 +1,54 @@
+import * as data from '@site/data/variables';
+import Setup from '@docs/shared/common/project-setup/index.mdx';
+import ProjectImplement from '@docs/shared/signaling/cloud-proxy/project-implementation/index.mdx';
+import ProjectTest from '@docs/shared/signaling/cloud-proxy/project-test/index.mdx';
+import Reference from '@docs/shared/signaling/cloud-proxy/reference/index.mdx';
+
+You use to ensure reliable connectivity for your users when they connect from an
+environment with a restricted network.
+
+## Understand the tech
+
+To accommodate your end users’ firewall settings and business needs, offers a HTTP proxy service. The
+following figure shows the workflow:
+
+![cloud proxy](/images/signaling/cloud-proxy.svg)
+
+The steps you need to implement in your are:
+
+1. Before connecting to , set to request a connection to .
+
+2. When the request succeeds, sends back the proxy information.
+
+3. sends signaling and media data to , which forwards this data to .
+
+4. sends signaling and media data to , which forwards it to .
+
+## Prerequisites
+
+To follow this page, you must:
+
+- Setup the [ reference app](/en/signaling/get-started/get-started-sdk#project-setup)
+
+- Contact support@agora.io and enable for your project.
+
+- Configure your firewall to allow communication through the [allowed IP address](../reference/cloud-proxy-allowed-ips).
+
+
+## Implement communication using
+
+This section shows how to use the to implement in your , step-by-step.
+
+
+
+## Test
+
+
+
+
+## Reference
+
+This section contains information that completes the information in this page, or points you to documentation that explains other aspects to this
+product.
+
+
diff --git a/shared/signaling/cloud-proxy/project-implementation/android.mdx b/shared/signaling/cloud-proxy/project-implementation/android.mdx
new file mode 100644
index 000000000..e67aac9a2
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/android.mdx
@@ -0,0 +1,19 @@
+
+1. **Set to connect to before you join a channel**
+
+ To access in a restricted network environment, call `setCloudProxy` and pass `0` as a parameter to select the automatic mode for transmission. The `setCloudProxy` method returns `0` upon
+ successful initiation of cloud proxy service.
+
+ To enable the service in your , in `/app/java/com.example./MainActivity`, add the following code after `agoraEngine = RtcEngine.create(config);`:
+
+ ``` java
+ // Start cloud proxy service and set automatic transmission mode.
+ int proxyStatus = agoraEngine.setCloudProxy(0);
+ if (proxyStatus == 0) {
+ showMessage("Proxy service started successfully");
+ } else {
+ showMessage("Proxy service failed with error :" + proxyStatus);
+ }
+ ```
+
+
diff --git a/shared/signaling/cloud-proxy/project-implementation/electron.mdx b/shared/signaling/cloud-proxy/project-implementation/electron.mdx
new file mode 100644
index 000000000..7419b6031
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/electron.mdx
@@ -0,0 +1,34 @@
+
+
+1. **Import the required modules**
+
+ In `preload.js`, add the following before `createAgoraRtcEngine,`:
+
+ ``` javascript
+ CloudProxyType,
+ ```
+
+2. **Enable the connection to **
+
+ To access in a restricted network environment, call `setCloudProxy` and set the force UDP transmission mode. To implement this logic, in `preload.js`, add the following code after
+ `agoraEngine.initialize({appId: appID});`:
+
+ ``` javascript
+ // Start cloud proxy service in the forced UDP mode.
+ agoraEngine.setCloudProxy(CloudProxyType.UdpProxy);
+ ```
+
+3. **Setup the cloud proxy callback function**
+
+ The triggers `onConnectionStateChanged` callback to indicate the successful initiation of cloud proxy service. To setup this callback, in `preload.js`, add the following code after `const EventHandles = {`:
+
+ ``` javascript
+ onConnectionStateChanged: (connection, state, reason) =>
+ {
+ if(reason == 0)
+ {
+ console.log("The SDK is connecting to the Agora edge server");
+ }
+ },
+ ```
+
diff --git a/shared/signaling/cloud-proxy/project-implementation/flutter.mdx b/shared/signaling/cloud-proxy/project-implementation/flutter.mdx
new file mode 100644
index 000000000..747fefa9e
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/flutter.mdx
@@ -0,0 +1,34 @@
+
+
+1. **Enable connection to **
+
+
+ To access in a restricted network environment, call `setCloudProxy` and set the transmission mode. To implement this logic, in `setupVoiceSDKEngine`, add the following code after calling `agoraEngine.initialize`:
+
+
+ To access in a restricted network environment, call `setCloudProxy` and set the transmission mode. To implement this logic, in `setupVideoSDKEngine`, add the following code after calling `agoraEngine.initialize`:
+
+
+ ``` dart
+ // Start cloud proxy service in forced UDP mode.
+ await agoraEngine.setCloudProxy(CloudProxyType.udpProxy);
+ ```
+
+1. **Set up the cloud proxy callback**
+
+
+ The triggers `onConnectionStateChanged` callback to indicate the successful initiation of cloud proxy service. To set up this callback, in `setupVoiceSDKEngine`, add the following code after `RtcEngineEventHandler(`:
+
+
+ The triggers `onConnectionStateChanged` callback to indicate the successful initiation of cloud proxy service. To set up this callback, in `setupVideoSDKEngine`, add the following code after `RtcEngineEventHandler(`:
+
+
+ ``` dart
+ onConnectionStateChanged: (RtcConnection connection, ConnectionStateType state,
+ ConnectionChangedReasonType reason) {
+ if (reason == ConnectionChangedReasonType.connectionChangedSettingProxyServer){
+ showMessage("Proxy settings changed");
+ }
+ },
+ ```
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/project-implementation/index.mdx b/shared/signaling/cloud-proxy/project-implementation/index.mdx
new file mode 100644
index 000000000..7b1ffffef
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/index.mdx
@@ -0,0 +1,20 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import ReactNative from './react-native.mdx';
+import Electron from './electron.mdx';
+import Flutter from './flutter.mdx';
+import MacOS from './macos.mdx'
+import Unity from './unity.mdx'
+import Windows from './windows.mdx';
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/project-implementation/ios.mdx b/shared/signaling/cloud-proxy/project-implementation/ios.mdx
new file mode 100644
index 000000000..e10531f95
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/ios.mdx
@@ -0,0 +1,8 @@
+import Code from './swift.mdx';
+
+
+
+
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-implementation/macos.mdx b/shared/signaling/cloud-proxy/project-implementation/macos.mdx
new file mode 100644
index 000000000..11043d017
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/macos.mdx
@@ -0,0 +1,8 @@
+import Code from './swift.mdx';
+
+
+
+
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-implementation/react-native.mdx b/shared/signaling/cloud-proxy/project-implementation/react-native.mdx
new file mode 100644
index 000000000..111b4383d
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/react-native.mdx
@@ -0,0 +1,32 @@
+
+
+1. **Import the required modules**
+
+ In `App.tsx`, add the following import before `createAgoraRtcEngine,`:
+
+ ``` javascript
+ CloudProxyType,
+ ```
+
+2. **Enable the connection to **
+
+ To access in a restricted network environment, call `setCloudProxy` and set the force UDP transmission mode. To implement this logic, in `App.tsx`, add the following code after
+ `agoraEngine.initialize({appId: appID});`:
+
+ ``` javascript
+ // Start cloud proxy service in the forced UDP mode.
+ agoraEngine.setCloudProxy(CloudProxyType.UdpProxy);
+ ```
+
+3. **Setup the cloud proxy callback function**
+
+ The triggers `onConnectionStateChanged` callback to indicate the successful initiation of cloud proxy service. To setup this callback, in `App.tsx`, add the following code after `onUserOffline: (_connection, remoteUid) => {`:
+
+ ``` javascript
+ onConnectionStateChanged(connection, state, reason) {
+ if (reason === 0) {
+ console.log('The SDK is connecting to the Agora edge server');
+ }
+ },
+ ```
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/project-implementation/swift.mdx b/shared/signaling/cloud-proxy/project-implementation/swift.mdx
new file mode 100644
index 000000000..0d060ad6d
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/swift.mdx
@@ -0,0 +1,15 @@
+
+1. **Start the service before you join a channel**
+
+ To access in a restricted network environment, call `setCloudProxy` and select the automatic mode for data transmission. The `setCloudProxy` method returns `0` upon successful initiation of
+ cloud proxy service.
+
+ To enable the cloud proxy service in your , in `ViewController`, add the following lines after `let option = AgoraRtcChannelMediaOptions()`:
+
+ ```swift
+ // Start cloud proxy service and select automatic mode for data transmission.
+ let status = agoraEngine.setCloudProxy(AgoraCloudProxyType.noneProxy)
+ if (status != 0) {
+ showMessage(title: "Cloud proxy status", text: "Proxy failed")
+ }
+ ```
diff --git a/shared/signaling/cloud-proxy/project-implementation/unity.mdx b/shared/signaling/cloud-proxy/project-implementation/unity.mdx
new file mode 100644
index 000000000..a31a4c9e6
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/unity.mdx
@@ -0,0 +1,19 @@
+
+1. **Set to connect to before you join a channel**
+
+ To access in a restricted network environment, call `SetCloudProxy` and pass `0` as a parameter to select the automatic mode for transmission. The `setCloudProxy` method returns `0` upon
+ successful initiation of cloud proxy service.
+
+ To enable the service in your , in your script file, locate `Start` and add the following code before `InitEventHandler();`:
+
+ ``` csharp
+ // Start cloud proxy service and set automatic transmission mode.
+ int proxyStatus = RtcEngine.SetCloudProxy(0);
+ if (proxyStatus == 0) {
+ Debug.Log("Proxy service started successfully");
+ } else {
+ Debug.Log("Proxy service failed with error :" + proxyStatus);
+ }
+ ```
+
+
diff --git a/shared/signaling/cloud-proxy/project-implementation/web.mdx b/shared/signaling/cloud-proxy/project-implementation/web.mdx
new file mode 100644
index 000000000..cd3c915c6
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/web.mdx
@@ -0,0 +1,15 @@
+
+
+### Set proxy configuration
+
+ ``` javascript
+ Set proxy configuration
+ ```
+
+### Apply the configuration to the
+
+ ``` javascript
+ signalingEngine = new AgoraRTM.RTM(config.appId, config.uid, rtmConfig);
+ ```
+
+
diff --git a/shared/signaling/cloud-proxy/project-implementation/windows.mdx b/shared/signaling/cloud-proxy/project-implementation/windows.mdx
new file mode 100644
index 000000000..730c1a8a7
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-implementation/windows.mdx
@@ -0,0 +1,21 @@
+
+1. **Set to connect to before you join a channel**
+
+ To access in a restricted network environment, call `setCloudProxy` and pass `0` as a parameter to select the automatic mode for transmission. The `setCloudProxy` method returns `0` upon
+ successful initiation of cloud proxy service.
+
+ To enable the service in your , in **Solution Explorer**, open `AgoraImplementationDlg.cpp` file and add the following code in `setupVideoSDKEngine()` after `agoraEngine->setClientRole(CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER);`:
+
+ ```cpp
+ // Start cloud proxy service and set automatic transmission mode
+ int proxyStatus = agoraEngine->setCloudProxy(CLOUD_PROXY_TYPE::NONE_PROXY);
+ if (proxyStatus == 0) {
+ AfxMessageBox(L"Proxy service started successfully");
+ } else {
+ CString message;
+ message.Format(_T("Proxy service failed with error: %d"), proxyStatus);
+ AfxMessageBox(message);
+ }
+ ```
+
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/project-setup/android.mdx b/shared/signaling/cloud-proxy/project-setup/android.mdx
new file mode 100644
index 000000000..c56ca9b9b
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-setup/android.mdx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-setup/electron.mdx b/shared/signaling/cloud-proxy/project-setup/electron.mdx
new file mode 100644
index 000000000..826e1e76d
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-setup/electron.mdx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-setup/flutter.mdx b/shared/signaling/cloud-proxy/project-setup/flutter.mdx
new file mode 100644
index 000000000..f2141600a
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-setup/flutter.mdx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-setup/index.mdx b/shared/signaling/cloud-proxy/project-setup/index.mdx
new file mode 100644
index 000000000..59182844a
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-setup/index.mdx
@@ -0,0 +1,17 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import ReactNative from './react-native.mdx';
+import Electron from './electron.mdx';
+import Flutter from './flutter.mdx';
+import MacOS from './macos.mdx'
+import Windows from './windows.mdx';
+
+
+
+
+
+
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-setup/ios.mdx b/shared/signaling/cloud-proxy/project-setup/ios.mdx
new file mode 100644
index 000000000..b17cd42dc
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-setup/ios.mdx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-setup/macos.mdx b/shared/signaling/cloud-proxy/project-setup/macos.mdx
new file mode 100644
index 000000000..6a8810f6d
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-setup/macos.mdx
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-setup/react-native.mdx b/shared/signaling/cloud-proxy/project-setup/react-native.mdx
new file mode 100644
index 000000000..25a8fca94
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-setup/react-native.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/project-setup/web.mdx b/shared/signaling/cloud-proxy/project-setup/web.mdx
new file mode 100644
index 000000000..8f737f379
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-setup/web.mdx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-setup/windows.mdx b/shared/signaling/cloud-proxy/project-setup/windows.mdx
new file mode 100644
index 000000000..e69de29bb
diff --git a/shared/signaling/cloud-proxy/project-test/android.mdx b/shared/signaling/cloud-proxy/project-test/android.mdx
new file mode 100644
index 000000000..02c772144
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-test/android.mdx
@@ -0,0 +1,23 @@
+
+
+3. In Android Studio, open `app/java/com.example./MainActivity`, and update `appId`, `channelName` and `token` with the values for your temporary token.
+
+4. Connect a physical Android device to your development device.
+
+5. In Android Studio, click **Run app**. A moment later you see the project installed on your device.
+
+ If this is the first time you run the project, grant microphone and camera access to your app.
+
+
+6. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+6. Click **Join** to start a call.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
diff --git a/shared/signaling/cloud-proxy/project-test/electron.mdx b/shared/signaling/cloud-proxy/project-test/electron.mdx
new file mode 100644
index 000000000..5ba3fe668
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-test/electron.mdx
@@ -0,0 +1,25 @@
+
+
+3. In _preload.js_, update `appID`, `channel` and `token` with your values.
+
+4. Run the app
+
+ Execute the following command in the terminal:
+
+ ```bash
+ npm start
+ ```
+ You see your app opens a window named **Get started with **.
+
+
+5. To join as a host, select **Host** and click **Join**.
+
+
+
+
+5. To connect to a channel, click **Join**.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
diff --git a/shared/signaling/cloud-proxy/project-test/flutter.mdx b/shared/signaling/cloud-proxy/project-test/flutter.mdx
new file mode 100644
index 000000000..62daa8bba
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-test/flutter.mdx
@@ -0,0 +1,23 @@
+
+
+3. In your IDE, open `main.dart`, and update `appId`, `channelName` and `token` with the values for your temporary token.
+
+4. Connect a test device to your development device.
+
+5. In your IDE, click *Run app* or execute `flutter run lib/main.dart`. A moment later you see the project installed on your device.
+
+ If this is the first time you run the project, grant microphone and camera access to your .
+
+
+6. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local stream is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+6. Click **Join** to start a call.
+
+
+ You see your connects to the from a restricted network environment using the cloud proxy service.
+
+
diff --git a/shared/signaling/cloud-proxy/project-test/index.mdx b/shared/signaling/cloud-proxy/project-test/index.mdx
new file mode 100644
index 000000000..f030391fe
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-test/index.mdx
@@ -0,0 +1,24 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import ReactNative from './react-native.mdx';
+import Electron from './electron.mdx';
+import Flutter from './flutter.mdx';
+import MacOS from './macos.mdx'
+import Unity from './unity.mdx'
+import Windows from './windows.mdx';
+import Test from '@docs/shared/common/project-test/index.mdx';
+
+To test this functionality:
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/project-test/ios.mdx b/shared/signaling/cloud-proxy/project-test/ios.mdx
new file mode 100644
index 000000000..f3011d0fa
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-test/ios.mdx
@@ -0,0 +1,24 @@
+
+
+3. In the `ViewController`, update `appID`, `channelName`, and `token` with the values from .
+
+4. Run your using either a physical or a simulator iOS device.
+
+ If this is the first time you run the project, grant microphone and camera access to your .
+
+ If you use an iOS simulator, you see the remote video only. You cannot see the local video stream because of Apple simulator hardware restrictions.
+
+
+5. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+
+You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-test/macos.mdx b/shared/signaling/cloud-proxy/project-test/macos.mdx
new file mode 100644
index 000000000..268fbc910
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-test/macos.mdx
@@ -0,0 +1,20 @@
+
+
+3. In the `ViewController`, update `appID`, `channelName`, and `token` with the values from .
+
+4. Run your .
+
+
+5. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+
+You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-test/react-native.mdx b/shared/signaling/cloud-proxy/project-test/react-native.mdx
new file mode 100644
index 000000000..ba94866cc
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-test/react-native.mdx
@@ -0,0 +1,38 @@
+
+3. In `App.tsx`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+1. Run your app:
+
+ - *Android*
+
+ 1. Enable Developer options on your Android device, and then connect it to your computer using a USB cable.
+ 1. Run `npx react-native run-android` in the project root directory.
+
+ - *iOS:*
+
+ 1. Open the `ProjectName/ios/ProjectName.xcworkspace` folder with Xcode.
+ 1. Connect your iOS device to your Mac using a USB cable.
+ 1. Click the **Build and run** button in Xcode.
+
+
+ If this is the first time you run the project, you need to grant microphone and camera access to your .
+
+
+5. Select an option and click **Join** to start a session.
+ - When you join as a **Host**, the local video is published and played in the .
+ - When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+Now, you can see yourself on the device screen and talk to the remote user using your .
+
+
+
+5. Click **Join** to start a call.
+
+Now, you can talk to the remote user using your .
+
+
+
diff --git a/shared/signaling/cloud-proxy/project-test/unity.mdx b/shared/signaling/cloud-proxy/project-test/unity.mdx
new file mode 100644
index 000000000..1557aa689
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-test/unity.mdx
@@ -0,0 +1,17 @@
+
+
+3. In your script file, update `_appID`, `_channelName` and `_token` with the values for your temporary token.
+
+4. In **Unity Editor**, click **Play**. A moment later you see the running on your development device.
+
+
+5. To join as a host, select **Host** and click **Join**.
+
+
+
+5. To connect to a channel, click **Join**.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/project-test/web.mdx b/shared/signaling/cloud-proxy/project-test/web.mdx
new file mode 100644
index 000000000..5f9791406
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-test/web.mdx
@@ -0,0 +1,7 @@
+
+
+4. **Test Cloud Proxy**
+
+ Login to , you send and receive messages through Cloud Proxy.
+
+
diff --git a/shared/signaling/cloud-proxy/project-test/windows.mdx b/shared/signaling/cloud-proxy/project-test/windows.mdx
new file mode 100644
index 000000000..22eade59e
--- /dev/null
+++ b/shared/signaling/cloud-proxy/project-test/windows.mdx
@@ -0,0 +1,20 @@
+
+ 3. In `CAgoraImplementationDlg.h`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+ 4. In Visual Studio, click **Local Windows Debugger**. A moment later you see the project running on your development device.
+
+ If this is the first time you run the project, you need to grant microphone and camera access to your .
+
+
+ 5. Select an option and click **Join** to start a session.
+ - When you join as a **Host**, the local video is published and played in the .
+ - When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+ 5. Click **Join** to start a call. Now, you can see yourself on the device screen and talk to the remote user using your .
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/reference/android.mdx b/shared/signaling/cloud-proxy/reference/android.mdx
new file mode 100644
index 000000000..30bc8406d
--- /dev/null
+++ b/shared/signaling/cloud-proxy/reference/android.mdx
@@ -0,0 +1,12 @@
+
+
+### API reference
+
+
+* setCloudProxy
+
+
+* setCloudProxy
+
+
+
diff --git a/shared/signaling/cloud-proxy/reference/electron.mdx b/shared/signaling/cloud-proxy/reference/electron.mdx
new file mode 100644
index 000000000..5afec5a3a
--- /dev/null
+++ b/shared/signaling/cloud-proxy/reference/electron.mdx
@@ -0,0 +1,19 @@
+
+
+### API reference
+
+
+
+- setCloudProxy
+- onConnectionStateChanged
+
+
+
+
+- setCloudProxy
+- onConnectionStateChanged
+
+
+
+
+
diff --git a/shared/signaling/cloud-proxy/reference/flutter.mdx b/shared/signaling/cloud-proxy/reference/flutter.mdx
new file mode 100644
index 000000000..9e2f904d0
--- /dev/null
+++ b/shared/signaling/cloud-proxy/reference/flutter.mdx
@@ -0,0 +1,17 @@
+
+
+### API reference
+
+
+* setCloudProxy
+
+* onConnectionStateChanged
+
+
+
+* setCloudProxy
+
+* onConnectionStateChanged
+
+
+
diff --git a/shared/signaling/cloud-proxy/reference/index.mdx b/shared/signaling/cloud-proxy/reference/index.mdx
new file mode 100644
index 000000000..7ae892546
--- /dev/null
+++ b/shared/signaling/cloud-proxy/reference/index.mdx
@@ -0,0 +1,19 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import ReactNative from './react-native.mdx';
+import Electron from './electron.mdx';
+import Flutter from './flutter.mdx';
+import MacOS from './macos.mdx'
+import Unity from './unity.mdx'
+import Windows from './windows.mdx';
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/reference/ios.mdx b/shared/signaling/cloud-proxy/reference/ios.mdx
new file mode 100644
index 000000000..103b2e211
--- /dev/null
+++ b/shared/signaling/cloud-proxy/reference/ios.mdx
@@ -0,0 +1,7 @@
+
+
+#### API reference
+
+* setCloudProxy
+
+
diff --git a/shared/signaling/cloud-proxy/reference/macos.mdx b/shared/signaling/cloud-proxy/reference/macos.mdx
new file mode 100644
index 000000000..93b97ab88
--- /dev/null
+++ b/shared/signaling/cloud-proxy/reference/macos.mdx
@@ -0,0 +1,7 @@
+
+
+#### API reference
+
+* setCloudProxy
+
+
diff --git a/shared/signaling/cloud-proxy/reference/react-native.mdx b/shared/signaling/cloud-proxy/reference/react-native.mdx
new file mode 100644
index 000000000..ad1b9afeb
--- /dev/null
+++ b/shared/signaling/cloud-proxy/reference/react-native.mdx
@@ -0,0 +1,14 @@
+
+
+### API reference
+
+
+- setCloudProxy
+- onConnectionStateChanged
+
+
+- setCloudProxy
+- onConnectionStateChanged
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/cloud-proxy/reference/unity.mdx b/shared/signaling/cloud-proxy/reference/unity.mdx
new file mode 100644
index 000000000..5d55f0d71
--- /dev/null
+++ b/shared/signaling/cloud-proxy/reference/unity.mdx
@@ -0,0 +1,16 @@
+import * as data from '@site/data/variables';
+
+
+#### API references
+
+
+- SetCloudProxy
+- CLOUD_PROXY_TYPE
+
+
+
+- SetCloudProxy
+- CLOUD_PROXY_TYPE
+
+
+
diff --git a/shared/signaling/cloud-proxy/reference/web.mdx b/shared/signaling/cloud-proxy/reference/web.mdx
new file mode 100644
index 000000000..eada26adb
--- /dev/null
+++ b/shared/signaling/cloud-proxy/reference/web.mdx
@@ -0,0 +1,5 @@
+
+
+- [ API reference](/en/signaling/reference/api)
+- [RTMConfig.cloudProxy](/en/signaling/reference/api?platform=web#rtmconfig)
+
diff --git a/shared/signaling/cloud-proxy/reference/windows.mdx b/shared/signaling/cloud-proxy/reference/windows.mdx
new file mode 100644
index 000000000..6ab51cd60
--- /dev/null
+++ b/shared/signaling/cloud-proxy/reference/windows.mdx
@@ -0,0 +1,7 @@
+
+
+#### API reference
+
+* setCloudProxy
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/_data_encryption.mdx b/shared/signaling/data-encryption/_data_encryption.mdx
new file mode 100644
index 000000000..b9cd07dfd
--- /dev/null
+++ b/shared/signaling/data-encryption/_data_encryption.mdx
@@ -0,0 +1,62 @@
+import * as data from '@site/data/variables';
+import Prerequites from '@docs/shared/common/prerequities.mdx';
+import ProjectSetup from '@docs/shared/signaling/data-encryption/project-setup/index.mdx';
+import ProjectImplement from '@docs/shared/signaling/data-encryption/project-implementation/index.mdx';
+import ProjectTest from '@docs/shared/signaling/data-encryption/project-test/index.mdx';
+import Reference from '@docs/shared/signaling/data-encryption/reference/index.mdx';
+
+
+Data encryption ensures that only the authorized users in a channel communicate with each other. This ensures that
+potential eavesdroppers cannot access sensitive and private information shared in a channel. While not every use
+case requires data encryption, provides built-in encryption methods that guarantee data confidentiality
+during transmission.
+
+This page shows you how to integrate built-in data encryption into your using .
+
+## Understand the tech
+
+The following figure shows the call flow for the data encryption:
+
+![Encrypt data ](/images/signaling/data-encryption.svg)
+
+All users in a channel must use the same encryption configuration to initiate `agoraEngine` and enable encryption before joining a channel. If you don’t have the correct configuration, you cannot decrypt channel content. Best practice is that your authentication system generates a new key and salt regularly.
+
+ provides security for user applications in the following ways:
+
+- **Transport layer encryption**: for data transmission between your and .
+- **Message encryption**: each message is protected with end-to-end AES_256_GCM encryption protection.
+- **Token authorization** - time-based access access control strategy.
+
+To ensure secure communication, your uses the same SSL [key](https://en.wikipedia.org/wiki/Public_key_certificate) and [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) to encrypt and
+decrypt data in the channel. You use the key and salt to create an encryption configuration. uses the encryption configuration to encrypt a stream and sends it to remote users. When the remote user receives
+the encrypted data stream, the remote decrypts the data stream using the same salt and key.
+
+If your must be highly secure, or meet security compliance standards like HIPAA or SOC 2
+type 2, use message-level encryption. For a higher levels combine TLS encryption with end-to-end AES encryption.
+
+## Prerequisites
+
+To follow this page, you must have:
+
+- Setup the [ reference app](/en/signaling/get-started/get-started-sdk#project-setup).
+
+* Installed the latest version of [OpenSSL](https://www.openssl.org/)
+
+
+## Implement data stream encryption
+
+To implement data encryption, do the following:
+
+
+
+## Test data encryption
+
+
+
+Communication between your test devices is end-to-end encrypted. This prevents data from being read or secretly modified by anyone other than the true sender and recipient.
+
+## Reference
+
+This section contains information that completes the information in this page, or points you to documentation that explains other aspects to this product.
+
+
diff --git a/shared/signaling/data-encryption/project-implementation/android.mdx b/shared/signaling/data-encryption/project-implementation/android.mdx
new file mode 100644
index 000000000..06b451121
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/android.mdx
@@ -0,0 +1,63 @@
+
+
+1. **Add the required imports**
+
+ In `/app/java/com.example./MainActivity`, add the following imports after the last import statement:
+
+ ``` java
+ import io.agora.rtc2.internal.EncryptionConfig;
+ import java.util.Base64;
+ ```
+
+ Base64 requires that you set the `Min SDK Version` property in your project to `26` or higher.
+
+2. **Add the required variables**
+
+ In `/app/java/com.example./MainActivity`, add the following declarations to `MainActivity` class:
+
+ ``` java
+ // In a production environment, you retrieve the key and salt from
+ // an authentication server. For this code example you generate them locally.
+
+ // A 32-byte string for encryption.
+ private String encryptionKey = "";
+ // A 32-byte string in Base64 format for encryption.
+ private String encryptionSaltBase64 = "";
+ ```
+
+3. **Add the media stream encryption method**
+
+ To enable media stream encryption in your , create an `EncryptionConfig` object and specify a key, salt, and encryption mode. Call `enableEncryption` and pass the `EncryptionConfig` object as a parameter.
+
+ In `/app/java/com.example./MainActivity`, add the following method to `MainActivity` class:
+
+ ``` java
+ public void enableEncryption()
+ {
+ if(encryptionSaltBase64 == null || encryptionKey == null)
+ return;
+ // Convert the salt string into bytes
+ byte[] encryptionSalt = Base64.getDecoder().decode(encryptionSaltBase64);
+ // An object to specify encryption configuration.
+ EncryptionConfig config = new EncryptionConfig();
+ // Specify an encryption mode.
+ config.encryptionMode = EncryptionConfig.EncryptionMode.AES_128_GCM2;
+ // Set secret key and salt.
+ config.encryptionKey = encryptionKey;
+ System.arraycopy(encryptionSalt, 0, config.encryptionKdfSalt, 0, config.encryptionKdfSalt.length);
+ // Call the method to enable media encryption.
+ if(agoraEngine.enableEncryption(true, config) == 0)
+ {
+ Toast.makeText(getApplicationContext(), "Media encryption enabled", Toast.LENGTH_SHORT).show();
+ }
+ }
+ ```
+
+4. **Start encryption before joining a channel**
+
+ In `/app/java/com.example./MainActivity`, add the following code at the end of `SetupVideoSDKEngine`:
+
+ ``` java
+ enableEncryption();
+ ```
+
diff --git a/shared/signaling/data-encryption/project-implementation/electron.mdx b/shared/signaling/data-encryption/project-implementation/electron.mdx
new file mode 100644
index 000000000..dd60a9ed2
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/electron.mdx
@@ -0,0 +1,52 @@
+
+
+1. **Add the required variables**
+
+ In `preload.js`, add the following to the declarations:
+
+ ``` javascript
+ // In a production environment, you retrieve the key and salt from
+ // an authentication server. For this code example you generate locally.
+
+ let encryptionKey = "";
+ let encryptionSaltBase64 = "";
+ ```
+
+2. **Import the required modules**
+
+ In `preload.js`, add the following before `createAgoraRtcEngine,`:
+
+ ```javascript
+ EncryptionMode,
+ ```
+
+3. **Call the channel encryption method to enable channel encryption**
+
+ To enable channel encryption in your , you need to:
+
+ 1. Convert the salt you generated using the OpenSSL command to a byte array.
+
+ 1. Create an object of `EncryptionConfig` class and specify a configuration for the channel encryption. In the configuration, you specify `encryptionMode`, `encryptionKey`, and `encryptionKdfSalt`.
+
+ 2. Call `enableEncryption` and pass `true` and the `EncryptionConfig` object as parameters to enable media encryption.
+
+ To implement this logic, in `preload.js`, add the following code after `agoraEngine.initialize({appId: appID});`
+
+ ``` javascript
+ const buffer = Buffer.from(encryptionSaltBase64, 'base64');
+ var salt = Array(buffer.length);
+ for (var i = 0; i < buffer.length; i++)
+ {
+ salt[i] = buffer[i];
+ }
+ let encryptionConfig =
+ {
+ encryptionKdfSalt: salt,
+ encryptionKey: encryptionKey,
+ encryptionMode: EncryptionMode.Aes128Gcm2
+ }
+ // Start the channel encryption
+ agoraEngine.enableEncryption(true,encryptionConfig);
+ ```
+ recommends using `Aes128Gcm2` or `Aes256Gcm2` encrypted mode. These two modes support the use of salt for higher security.
+
diff --git a/shared/signaling/data-encryption/project-implementation/flutter.mdx b/shared/signaling/data-encryption/project-implementation/flutter.mdx
new file mode 100644
index 000000000..1c0d7bc62
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/flutter.mdx
@@ -0,0 +1,62 @@
+
+
+1. **Add the required libraries**
+
+ In `/lib/main.dart`, add the following lines after the last import statement:
+
+ ``` dart
+ import 'dart:convert';
+ import 'dart:typed_data';
+ ```
+
+1. **Declare the required variables**
+
+ In a production environment, you retrieve the encryption key and salt from an authentication server. For this simple example, you generate them locally using `OpenSSL` and insert in your code. In `/lib/main.dart`, add the following declarations to the `_MyAppState` class:
+
+ ``` dart
+ // A 32-byte string for encryption.
+ String encryptionKey = "";
+ // A 32-byte string in Base64 format for encryption.
+ String encryptionSaltBase64 = "";
+ ```
+
+1. **Add the media stream encryption method**
+
+ To enable media stream encryption in your , you create an `EncryptionConfig` object using an encryption key, salt, and encryption mode. You pass this configuration object as a parameter to the `enableEncryption` method.
+
+ In `/lib/main.dart`, add the following method to the `_MyAppState` class:
+
+ ``` dart
+ void enableEncryption() {
+ if (encryptionSaltBase64.isEmpty || encryptionKey.isEmpty) {
+ showMessage("Please set encryption key and salt");
+ return;
+ }
+
+ // Convert the salt string into the required format
+ Uint8List bytes = base64Decode(encryptionSaltBase64);
+
+ // An object to specify encryption configuration.
+ EncryptionConfig config = EncryptionConfig(
+ encryptionMode: EncryptionMode.aes128Gcm2,
+ encryptionKey: encryptionKey,
+ encryptionKdfSalt: bytes
+ );
+
+ // Enable media encryption using the configuration
+ agoraEngine.enableEncryption(
+ enabled: true, config: config);
+
+ showMessage("Media encryption enabled");
+ }
+ ```
+
+1. **Start encryption before joining a channel**
+
+ In the `join()` method, add the following line before `agoraEngine.joinChannel`:
+
+ ``` dart
+ enableEncryption();
+ ```
+
+
diff --git a/shared/signaling/data-encryption/project-implementation/index.mdx b/shared/signaling/data-encryption/project-implementation/index.mdx
new file mode 100644
index 000000000..9e8c5a5cf
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/index.mdx
@@ -0,0 +1,19 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import Electron from './electron.mdx';
+import Unity from './unity.mdx';
+import Flutter from './flutter.mdx';
+import ReactNative from './react-native.mdx';
+import MacOS from './macos.mdx';
+import Windows from './windows.mdx';
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-implementation/ios.mdx b/shared/signaling/data-encryption/project-implementation/ios.mdx
new file mode 100644
index 000000000..170d386ef
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/ios.mdx
@@ -0,0 +1,8 @@
+import Source from './swift.mdx';
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-implementation/macos.mdx b/shared/signaling/data-encryption/project-implementation/macos.mdx
new file mode 100644
index 000000000..74e608cdd
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/macos.mdx
@@ -0,0 +1,8 @@
+import Source from './swift.mdx';
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-implementation/react-native.mdx b/shared/signaling/data-encryption/project-implementation/react-native.mdx
new file mode 100644
index 000000000..e2f8372f5
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/react-native.mdx
@@ -0,0 +1,40 @@
+
+1. **Add the required variables**
+
+ In `App.tsx`, add the following to the declarations:
+
+ ```typescript
+ // In a production environment, you retrieve the key and salt from
+ // an authentication server. For this code example you generate locally.
+ const encryptionKey = '';
+ const encryptionSaltBase64 = '';
+ ```
+
+2. **Import the required modules**
+
+ In `App.tsx`, add the following import in `import {} from 'react-native-agora-rtc-ng';`:
+
+ ```typescript
+ EncryptionMode,
+ ```
+
+3. **Call the channel encryption method to enable channel encryption**
+
+ To enable channel encryption in your , you need to:
+
+ 1. Create an `EncryptionConfig` instance and specify a configuration for the channel encryption. In the configuration, you specify `encryptionMode`, `encryptionKey`, and `encryptionKdfSalt`. `encryptionKdfSalt` is required for `Aes128Gcm2` and `Aes256Gcm2` modes, it is optional for other encryption modes.
+
+ 2. To enable media encryption, call `enableEncryption` with your `EncryptionConfig` instance.
+
+ To implement this logic, in `App.tsx`, add the following code after `agoraEngine.initialize({appId: appID});`
+
+ ```typescript
+ let encryptionConfig = {
+ encryptionBase64: encryptionBase64,
+ encryptionKey: encryptionKey,
+ encryptionMode: EncryptionMode.Aes128Ecb,
+ };
+ engine.enableEncryption(true, encryptionConfig);
+ ```
+ Best practice is to use Aes128Gcm2 or Aes256Gcm2. These modes use salt for higher security.
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-implementation/swift.mdx b/shared/signaling/data-encryption/project-implementation/swift.mdx
new file mode 100644
index 000000000..13b4e9d04
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/swift.mdx
@@ -0,0 +1,49 @@
+
+1. **Add the required variables**
+
+ Add the following declarations to the top of the `ViewController` class:
+
+ ``` swift
+ // In a production environment, you retrieve the key and salt from
+ // an authentication server. For this code example you generate locally.
+
+ // A 32-byte string for encryption.
+ var encryptionKey = ""
+ // A 32-byte string in Base64 format for encryption.
+ var encryptionSaltBase64 = ""
+ ```
+
+2. **Add the media stream encryption method**
+
+ To enable media stream encryption in your , create an `AgoraEncryptionConfig` object and specify a key, salt, and encryption mode. Call `enableEncryption` and pass the `EncryptionConfig` object as a parameter.
+
+ In `ViewController` class, add the following function:
+
+ ``` swift
+ func enableEncryption() {
+ // Convert the salt string in the Base64 format into bytes
+ let encryptionSalt: Data = Data(base64Encoded: encryptionSaltBase64, options: .ignoreUnknownCharacters)!
+
+ // An object to specify encryption configuration.
+ let config = AgoraEncryptionConfig()
+
+ // Specify an encryption mode.
+ config.encryptionMode = AgoraEncryptionMode.AES128GCM2
+ // Set secret key and salt.
+ config.encryptionKey = encryptionKey
+ config.encryptionKdfSalt = encryptionSalt
+
+ // Call the method to enable media encryption.
+ if (agoraEngine.enableEncryption(true, encryptionConfig: config) == 0) {
+ print("Media encryption enabled.")
+ }
+ }
+ ```
+
+3. **Start encryption before joining a channel**
+
+ Add the following line to the end of `initializeAgoraEngine` function:
+
+ ``` swift
+ enableEncryption()
+ ```
diff --git a/shared/signaling/data-encryption/project-implementation/unity.mdx b/shared/signaling/data-encryption/project-implementation/unity.mdx
new file mode 100644
index 000000000..655109e48
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/unity.mdx
@@ -0,0 +1,57 @@
+
+
+1. **Add the required imports**
+
+ In your script file, add the following to after `using UnityEngine;`:
+
+ ``` csharp
+ using System;
+ ```
+
+2. **Add the required variables**
+
+ In your script file, add the following to declarations:
+
+ ``` csharp
+ // In a production environment, you retrieve the key and salt from
+ // an authentication server. For this code example you generate them locally.
+
+ string secretKey = "";
+ string salt = "";
+ ```
+
+3. **Add the media stream encryption method**
+
+ To enable media stream encryption in your , create an `EncryptionConfig` instance and specify a key, salt, and encryption mode. Call `EnableEncryption` and pass the instance of `EncryptionConfig` as a parameter.
+
+ In your dcript file, add the following method before `SetupVideoSDKEngine`:
+
+ ``` csharp
+ void enableEncryption()
+ {
+ if (RtcEngine != null)
+ {
+ // Create an encryption configuration.
+ var config = new EncryptionConfig
+ {
+ // Specify a encyption mode
+ encryptionMode = ENCRYPTION_MODE.AES_128_GCM2,
+ // Assign a secret key.
+ encryptionKey = secretKey,
+ // Assign a salt in Base64 format
+ encryptionKdfSalt = Convert.FromBase64String(salt)
+ };
+ // Enable the built-in encryption.
+ RtcEngine.EnableEncryption(true, config);
+ }
+ }
+ ```
+
+4. **Start encryption before joining a channel**
+
+ In your script file, add the following code at the end of `SetupVideoSDKEngine`:
+
+ ``` csharp
+ enableEncryption();
+ ```
+
diff --git a/shared/signaling/data-encryption/project-implementation/web.mdx b/shared/signaling/data-encryption/project-implementation/web.mdx
new file mode 100644
index 000000000..c3acd6ef3
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/web.mdx
@@ -0,0 +1,70 @@
+
+
+### Set encryption configuration
+
+1. **Add the required variables**
+
+ These variables hold the encrypted values of the salt and cypher key in your configuration.
+
+ ``` javascript
+ // In a production environment, you retrieve the key and salt from
+ // an authentication server. For this code example you generate locally.
+
+ var encryptionKey = "";
+ var encryptionSaltBase64 = "";
+ var encryptionMode = "";
+ ```
+
+2. **Add a method to convert a string from `Base64` to `Uint8Array`**
+
+
+ ``` javascript
+ function base64ToUint8Array(base64Str) {
+ const raw = window.atob(base64Str);
+ const result = new Uint8Array(new ArrayBuffer(raw.length));
+ for (let i = 0; i < raw.length; i += 1) {
+ result[i] = raw.charCodeAt(i);
+ }
+ return result;
+ }
+ ```
+
+3. **Add a method to convert a string from `Hex` to `ASCII`**
+
+ ``` javascript
+ function hex2ascii(hexx)
+ {
+ const hex = hexx.toString();//force conversion
+ let str = '';
+ for (let i = 0; i < hex.length; i += 2)
+ str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
+ return str;
+ }
+ ```
+
+4. **Create the configuration object**
+
+ ``` javascript
+ // Convert the encryptionSaltBase64 string to base64ToUint8Array.
+ encryptionSaltBase64 = base64ToUint8Array(config.salt);
+ // Convert the encryptionKey string to hex2ascii.
+ encryptionKey = hex2ascii(config.cipherKey);
+ // Set an encryption mode.
+ encryptionMode = config.encryptionMode;
+ // Start channel encryption
+ const rtmConfig = {
+ logLevel: config.logLevel,
+ useStringUserId: config.useStringUserId,
+ encryptionMode: config.encryptionMode,
+ salt: encryptionSaltBase64,
+ cipherKey: encryptionKey,
+ };
+ ```
+
+### Apply the configuration to the
+
+ ``` javascript
+ signalingEngine = new AgoraRTM.RTM(config.appId, config.uid, rtmConfig);
+ ```
+
+
diff --git a/shared/signaling/data-encryption/project-implementation/windows.mdx b/shared/signaling/data-encryption/project-implementation/windows.mdx
new file mode 100644
index 000000000..51f9e3c8d
--- /dev/null
+++ b/shared/signaling/data-encryption/project-implementation/windows.mdx
@@ -0,0 +1,58 @@
+
+
+1. **Add the required variables**
+
+ In `AgoraImplementationDlg.h`, add the following declarations to `CAgoraImplementationDlg`:
+
+ ```cpp
+ // In a production environment, you retrieve the key and salt from
+ // an authentication server. For this code example you generate them locally.
+
+ // A 32-byte string for encryption.
+ std::string encryptionKey = "";
+ // A 32-byte string in Base64 format for encryption.
+ std::string encryptionSaltBase64 = "";
+ ```
+
+3. **Add the media stream encryption method**
+
+ To enable media stream encryption in your , create an `EncryptionConfig` object and specify a key, salt, and encryption mode. Call `enableEncryption` and pass the `EncryptionConfig` object as a parameter. To implement this logic, take the following steps:
+
+ 1. In `AgoraImplementationDlg.cpp`, add the following method before `OnInitDialog`:
+
+ ```cpp
+ void CAgoraImplementationDlg::enableEncryption()
+ {
+ if (encryptionSaltBase64 == "" || encryptionKey == "")
+ return;
+ //set encrypt mode and encrypt secret
+ EncryptionConfig config;
+ config.encryptionMode = AES_256_GCM2;
+ config.encryptionKey = encryptionKey.c_str();
+ memcpy(config.encryptionKdfSalt, encryptionSaltBase64.c_str(), 32);
+ // Call the method to enable media encryption.
+ if (agoraEngine->enableEncryption(true, config) == 0)
+ {
+ AfxMessageBox(L"Encryption Enabled");
+ }
+ }
+ ```
+
+ 2. In `AgoraImplementationDlg.h`, add the following method declaration to `CAgoraImplementationDlg`:
+
+ ```cpp
+ void enableEncryption();
+ ```
+
+4. **Start media encryption before joining a channel**
+
+ In `AgoraImplementationDlg.cpp`, add the following code at the end of `SetupVoiceSDKEngine`:
+
+
+
+ In `AgoraImplementationDlg.cpp`, add the following code at the end of `SetupVideoSDKEngine`:
+
+ ``` cpp
+ enableEncryption();
+ ```
+
diff --git a/shared/signaling/data-encryption/project-setup/android.mdx b/shared/signaling/data-encryption/project-setup/android.mdx
new file mode 100644
index 000000000..66dac7397
--- /dev/null
+++ b/shared/signaling/data-encryption/project-setup/android.mdx
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/shared/signaling/release-notes/electron.mdx b/shared/signaling/data-encryption/project-setup/electron.mdx
similarity index 98%
rename from shared/signaling/release-notes/electron.mdx
rename to shared/signaling/data-encryption/project-setup/electron.mdx
index 43851d807..d1cb5e40f 100644
--- a/shared/signaling/release-notes/electron.mdx
+++ b/shared/signaling/data-encryption/project-setup/electron.mdx
@@ -1,4 +1,3 @@
-
diff --git a/shared/signaling/release-notes/flutter.mdx b/shared/signaling/data-encryption/project-setup/flutter.mdx
similarity index 100%
rename from shared/signaling/release-notes/flutter.mdx
rename to shared/signaling/data-encryption/project-setup/flutter.mdx
diff --git a/shared/signaling/data-encryption/project-setup/index.mdx b/shared/signaling/data-encryption/project-setup/index.mdx
new file mode 100644
index 000000000..acab29c6d
--- /dev/null
+++ b/shared/signaling/data-encryption/project-setup/index.mdx
@@ -0,0 +1,17 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import Unity from './unity.mdx';
+import ReactNative from './react-native.mdx'
+import Electron from './electron.mdx';
+import Flutter from './flutter.mdx';
+import MacOS from './macos.mdx';
+
+
+
+
+
+
+
+
+
diff --git a/shared/signaling/data-encryption/project-setup/ios.mdx b/shared/signaling/data-encryption/project-setup/ios.mdx
new file mode 100644
index 000000000..53b70930e
--- /dev/null
+++ b/shared/signaling/data-encryption/project-setup/ios.mdx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/shared/signaling/data-encryption/project-setup/macos.mdx b/shared/signaling/data-encryption/project-setup/macos.mdx
new file mode 100644
index 000000000..01184fd38
--- /dev/null
+++ b/shared/signaling/data-encryption/project-setup/macos.mdx
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-setup/react-native.mdx b/shared/signaling/data-encryption/project-setup/react-native.mdx
new file mode 100644
index 000000000..25a8fca94
--- /dev/null
+++ b/shared/signaling/data-encryption/project-setup/react-native.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-setup/unity.mdx b/shared/signaling/data-encryption/project-setup/unity.mdx
new file mode 100644
index 000000000..daf235c54
--- /dev/null
+++ b/shared/signaling/data-encryption/project-setup/unity.mdx
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-setup/web.mdx b/shared/signaling/data-encryption/project-setup/web.mdx
new file mode 100644
index 000000000..44170f8d8
--- /dev/null
+++ b/shared/signaling/data-encryption/project-setup/web.mdx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/shared/signaling/data-encryption/project-test/android.mdx b/shared/signaling/data-encryption/project-test/android.mdx
new file mode 100644
index 000000000..f158e5de4
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/android.mdx
@@ -0,0 +1,24 @@
+
+
+4. In Android Studio, in `app/java/com.example.\/MainActivity`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+5. Connect a physical Android device to your development machine.
+
+6. In Android Studio, click **Run app**. You see the running on your device.
+
+ If this is the first time you run your , grant microphone and camera access.
+
+7. Copy and install the `apk` for your on a second Android test device.
+
+
+8. Select **Broadcaster** mode on the test device and **Audience** on the development device.
+
+9. Press **Join** on both Android devices to join the same channel.
+
+
+
+8. Press **Join** on both Android devices to join the same channel.
+
+
+ You see the local and remote videos on the two devices.
+
diff --git a/shared/signaling/data-encryption/project-test/electron.mdx b/shared/signaling/data-encryption/project-test/electron.mdx
new file mode 100644
index 000000000..3782a951f
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/electron.mdx
@@ -0,0 +1,28 @@
+
+
+4. In *main.js*, update `appID`, `channel` and `token` with your values.
+
+5. Start the dev server
+
+ Execute the following command in the terminal:
+
+ ``` bash
+ npm start
+ ```
+
+ You see your app opens a window named **Get started with **.
+
+
+6. Select a user role using the radio buttons and click **Join**.
+
+
+
+
+6. To connect to a channel, click **Join**.
+
+
+
+7. Open another instance of your on a test device and update `appID`, `channel` and `token` with your values, then click **Join**.
+
+ You see the local and remote videos on the two devices.
+
diff --git a/shared/signaling/data-encryption/project-test/flutter.mdx b/shared/signaling/data-encryption/project-test/flutter.mdx
new file mode 100644
index 000000000..7f52de18c
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/flutter.mdx
@@ -0,0 +1,27 @@
+
+
+4. In `/lib/main.dart`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+5. Connect a test device to your development machine.
+
+6. In your IDE, click **Run app** or execute `flutter run`. You see the running on your device.
+
+ If this is the first time you run your , grant microphone and camera access.
+
+7. Copy and install the package for your on a second test device.
+
+
+8. Select **Host** mode on one test device and **Audience** on the other.
+
+9. Press **Join** on both devices to join the same channel.
+
+ You see local and remote videos on the two devices.
+
+
+
+8. Press **Join** on both Android devices to join the same channel.
+
+ You see local and remote videos on the two devices.
+
+
+
diff --git a/shared/signaling/data-encryption/project-test/index.mdx b/shared/signaling/data-encryption/project-test/index.mdx
new file mode 100644
index 000000000..334e2ed32
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/index.mdx
@@ -0,0 +1,52 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import Electron from './electron.mdx';
+import Unity from './unity.mdx';
+import Flutter from './flutter.mdx';
+import ReactNative from './react-native.mdx'
+import MacOS from './macos.mdx';
+import Windows from './windows.mdx';
+import Test from '@docs/shared/common/project-test/index.mdx';
+
+To test this functionality:
+
+
+1. **Create the cypher key and salt**
+
+ 1. Create the 32-byte key with the following command:
+
+ ``` bash
+ openssl rand -hex 32
+ ```
+
+ 2. Create the 64-byte salt with the following command
+
+ ``` bash
+ openssl rand -base64 32
+ ```
+1. **Configure data encryption**
+
+ In `/src/signaling_manager/config.json`:
+
+ 1. Paste the `-hex 32 key` into the `cipherKey` variable.
+
+ 1. Paste the salt from the `-base64 32` call into the `salt` variable.
+
+ 1. Set `encryptionMode` to 1.
+
+
+
+
+4. **Test data encryption**
+
+ Login to as multiple users, then send and receive secure messages.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-test/ios.mdx b/shared/signaling/data-encryption/project-test/ios.mdx
new file mode 100644
index 000000000..170d386ef
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/ios.mdx
@@ -0,0 +1,8 @@
+import Source from './swift.mdx';
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-test/macos.mdx b/shared/signaling/data-encryption/project-test/macos.mdx
new file mode 100644
index 000000000..74e608cdd
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/macos.mdx
@@ -0,0 +1,8 @@
+import Source from './swift.mdx';
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-test/react-native.mdx b/shared/signaling/data-encryption/project-test/react-native.mdx
new file mode 100644
index 000000000..306c0460a
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/react-native.mdx
@@ -0,0 +1,12 @@
+
+4. In *App.tsx*, update `appId`, `channelName` and `token` with your values.
+
+- *Android*
+
+ 1. Enable the Developer options on your Android device, and then connect it.
+ 1. Run npx react-native run-android in the project root directory.
+
+- *iOS:*
+
+ 1. In Xcode, run your project.
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/project-test/swift.mdx b/shared/signaling/data-encryption/project-test/swift.mdx
new file mode 100644
index 000000000..72f9cd6e4
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/swift.mdx
@@ -0,0 +1,17 @@
+
+4. In Xcode, in `ViewController`, update `appID`, `channelName` and `token` with the values for your temporary token.
+
+5. Run your , then wait a few seconds until the installation is complete.
+
+ If this is the first time you run the project, grant microphone and camera access to your .
+
+ If you use an iOS simulator, you see the remote video only. You cannot see the local video stream because of [Apple simulator hardware restrictions](https://help.apple.com/simulator/mac/current/#/devb0244142d).
+
+
+6. Select an option and click **Join** to start a session. When you join as a **Host**, the local video is published and played in the . When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+6. Click **Join** to start a call. Now, you can see yourself on the test device and talk to the web demo app using your .
+
+
diff --git a/shared/signaling/data-encryption/project-test/unity.mdx b/shared/signaling/data-encryption/project-test/unity.mdx
new file mode 100644
index 000000000..1ee6053ea
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/unity.mdx
@@ -0,0 +1,24 @@
+
+
+4. In your script file, update `_appID`, `_channelName` and `_token` with the values for your temporary token.
+
+5. In Unity Editor, build the project and then install the on a test device.
+
+6. Run the on the test device.
+
+7. In Unity Editor, click **Play**. You see the running on your development device.
+
+ If this is the first time you run your , grant microphone and camera access.
+
+
+8. Select **Broadcaster** mode on the test device and **Audience** on the development device.
+
+9. Press **Join** on both devices to join the same channel.
+
+
+
+8. Press **Join** on both devices to join the same channel.
+
+
+ You see the local and remote videos on the two devices.
+
diff --git a/shared/signaling/data-encryption/project-test/web.mdx b/shared/signaling/data-encryption/project-test/web.mdx
new file mode 100644
index 000000000..cdd010fd9
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/web.mdx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/shared/signaling/data-encryption/project-test/windows.mdx b/shared/signaling/data-encryption/project-test/windows.mdx
new file mode 100644
index 000000000..41e5f0a78
--- /dev/null
+++ b/shared/signaling/data-encryption/project-test/windows.mdx
@@ -0,0 +1,36 @@
+
+
+4. In `AgoraImplementationDlg.h`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+5. In Visual Studio, click **Local Window Debugger**. A moment later you see the project running on your development device.
+
+
+6. Select an option and click **Join** to start a session.
+ - When you join as a **Host**, the local video is published and played in the .
+ - When you join as **Audience**, the remote stream is subscribed and played.
+
+ If this is the first time you run the project, you need to grant microphone and camera access to your .
+
+
+
+
+
+6. Click **Join** to start .
+
+ If this is the first time you run the project, you need to grant microphone and camera access to your .
+
+
+
+
+
+6. Click **Join** to start .
+
+ If this is the first time you run the project, you need to grant microphone access to your .
+
+
+
+
+7. Open another instance of your on a test device and update `appId`, `channelName` and `token` with your values, then click **Join**.
+
+
+
diff --git a/shared/signaling/data-encryption/reference/android.mdx b/shared/signaling/data-encryption/reference/android.mdx
new file mode 100644
index 000000000..45599d9e4
--- /dev/null
+++ b/shared/signaling/data-encryption/reference/android.mdx
@@ -0,0 +1,16 @@
+
+
+### API reference
+
+
+- enableEncryption
+- EncryptionConfig
+
+
+
+- enableEncryption
+- EncryptionConfig
+
+
+
+
diff --git a/shared/signaling/data-encryption/reference/electron.mdx b/shared/signaling/data-encryption/reference/electron.mdx
new file mode 100644
index 000000000..2740919ae
--- /dev/null
+++ b/shared/signaling/data-encryption/reference/electron.mdx
@@ -0,0 +1,19 @@
+import * as data from '@site/data/variables';
+
+
+
+
+### API reference
+
+
+
+- enableEncryption
+- EncryptionConfig
+
+
+
+- enableEncryption
+- EncryptionConfig
+
+
+
diff --git a/shared/signaling/data-encryption/reference/flutter.mdx b/shared/signaling/data-encryption/reference/flutter.mdx
new file mode 100644
index 000000000..03f91d766
--- /dev/null
+++ b/shared/signaling/data-encryption/reference/flutter.mdx
@@ -0,0 +1,16 @@
+
+
+### API reference
+
+* enableEncryption
+
+* EncryptionConfig
+
+
+
+* enableEncryption
+
+* EncryptionConfig
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/reference/index.mdx b/shared/signaling/data-encryption/reference/index.mdx
new file mode 100644
index 000000000..0be29d57a
--- /dev/null
+++ b/shared/signaling/data-encryption/reference/index.mdx
@@ -0,0 +1,20 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import Electron from './electron.mdx';
+import Unity from './unity.mdx';
+import Flutter from './flutter.mdx';
+import ReactNative from './react-native.mdx';
+import MacOS from './macos.mdx';
+import Windows from './windows.mdx';
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/reference/ios.mdx b/shared/signaling/data-encryption/reference/ios.mdx
new file mode 100644
index 000000000..bf4e083ee
--- /dev/null
+++ b/shared/signaling/data-encryption/reference/ios.mdx
@@ -0,0 +1,8 @@
+
+
+### API reference
+
+- enableEncryption
+- EncryptionConfig
+
+
diff --git a/shared/signaling/data-encryption/reference/macos.mdx b/shared/signaling/data-encryption/reference/macos.mdx
new file mode 100644
index 000000000..c9b3b3957
--- /dev/null
+++ b/shared/signaling/data-encryption/reference/macos.mdx
@@ -0,0 +1,8 @@
+
+
+### API reference
+
+- enableEncryption
+- EncryptionConfig
+
+
diff --git a/shared/signaling/data-encryption/reference/react-native.mdx b/shared/signaling/data-encryption/reference/react-native.mdx
new file mode 100644
index 000000000..bf7551f12
--- /dev/null
+++ b/shared/signaling/data-encryption/reference/react-native.mdx
@@ -0,0 +1,14 @@
+
+### API reference
+
+
+- enableEncryption
+- EncryptionConfig
+
+
+
+- enableEncryption
+- EncryptionConfig
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/reference/unity.mdx b/shared/signaling/data-encryption/reference/unity.mdx
new file mode 100644
index 000000000..e7a4d1fba
--- /dev/null
+++ b/shared/signaling/data-encryption/reference/unity.mdx
@@ -0,0 +1,7 @@
+
+
+ ### API reference
+
+ - EnableEncryption
+ - EncryptionConfig
+
\ No newline at end of file
diff --git a/shared/signaling/data-encryption/reference/web.mdx b/shared/signaling/data-encryption/reference/web.mdx
new file mode 100644
index 000000000..42f78007d
--- /dev/null
+++ b/shared/signaling/data-encryption/reference/web.mdx
@@ -0,0 +1,5 @@
+
+
+- [ API reference](/en/signaling/reference/api)
+
+
diff --git a/shared/signaling/data-encryption/reference/windows.mdx b/shared/signaling/data-encryption/reference/windows.mdx
new file mode 100644
index 000000000..c8f885bd0
--- /dev/null
+++ b/shared/signaling/data-encryption/reference/windows.mdx
@@ -0,0 +1,16 @@
+
+
+### API reference
+
+
+- enableEncryption
+- EncryptionConfig
+
+
+
+- enableEncryption
+- EncryptionConfig
+
+
+
+
diff --git a/shared/signaling/geofencing/index.mdx b/shared/signaling/geofencing/index.mdx
new file mode 100644
index 000000000..8f707a781
--- /dev/null
+++ b/shared/signaling/geofencing/index.mdx
@@ -0,0 +1,55 @@
+import Code from '@docs/shared/signaling/geofencing/sample-code/index.mdx';
+import ProjectTest from '@docs/shared/signaling/geofencing/project-test/index.mdx';
+import Reference from '@docs/shared/signaling/geofencing/reference/index.mdx';
+
+
+export const toc = [{}];
+
+
+When a user joins a channel, automatically connects them to the closest geographical region of . However, to meet the laws and regulations of your region, you may want to filter in or filter out connections to a specific geographical region. geofencing enables you to control and customize data routing in your by specifying the region users connect to.
+
+## Understand the tech
+
+This section shows you how to enable geofencing in your . The following figure shows the workflow you implement for geofencing:
+
+![Network Geofencing](/images/common/geofencing.png)
+
+## Prerequisites
+
+In order to follow this procedure you must have:
+
+- Implemented the [](../get-started/get-started-sdk) project for .
+
+
+## Implement geofencing in your
+
+This section shows how to use the to implement geofencing in your .
+
+### Set the geofencing configuration
+
+Call `AgoraRTM.setArea` to specify the region to connect to. After specifying the region, connects
+to the Agora servers within that region.
+
+
+
+The following regions are supported:
+
+- `GLOBAL`: (Default) Global.
+- `CHINA`: Mainland China.
+- `ASIA`: Asia excluding mainland China.
+- `EUROPE`: Europe.
+- `INDIA`: India.
+- `JAPAN`: Japan.
+- `NORTH_AMERICA`: North America.
+
+The following code shows you how to set the geolocation:
+
+## Test geofencing
+
+
+
+## Reference
+
+This section contains information that completes the information in this page, or points you to documentation that explains other aspects to this product.
+
+
diff --git a/shared/signaling/geofencing/project-test/android.mdx b/shared/signaling/geofencing/project-test/android.mdx
new file mode 100644
index 000000000..02c772144
--- /dev/null
+++ b/shared/signaling/geofencing/project-test/android.mdx
@@ -0,0 +1,23 @@
+
+
+3. In Android Studio, open `app/java/com.example./MainActivity`, and update `appId`, `channelName` and `token` with the values for your temporary token.
+
+4. Connect a physical Android device to your development device.
+
+5. In Android Studio, click **Run app**. A moment later you see the project installed on your device.
+
+ If this is the first time you run the project, grant microphone and camera access to your app.
+
+
+6. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+6. Click **Join** to start a call.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
diff --git a/shared/signaling/geofencing/project-test/electron.mdx b/shared/signaling/geofencing/project-test/electron.mdx
new file mode 100644
index 000000000..5ba3fe668
--- /dev/null
+++ b/shared/signaling/geofencing/project-test/electron.mdx
@@ -0,0 +1,25 @@
+
+
+3. In _preload.js_, update `appID`, `channel` and `token` with your values.
+
+4. Run the app
+
+ Execute the following command in the terminal:
+
+ ```bash
+ npm start
+ ```
+ You see your app opens a window named **Get started with **.
+
+
+5. To join as a host, select **Host** and click **Join**.
+
+
+
+
+5. To connect to a channel, click **Join**.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
diff --git a/shared/signaling/geofencing/project-test/flutter.mdx b/shared/signaling/geofencing/project-test/flutter.mdx
new file mode 100644
index 000000000..62daa8bba
--- /dev/null
+++ b/shared/signaling/geofencing/project-test/flutter.mdx
@@ -0,0 +1,23 @@
+
+
+3. In your IDE, open `main.dart`, and update `appId`, `channelName` and `token` with the values for your temporary token.
+
+4. Connect a test device to your development device.
+
+5. In your IDE, click *Run app* or execute `flutter run lib/main.dart`. A moment later you see the project installed on your device.
+
+ If this is the first time you run the project, grant microphone and camera access to your .
+
+
+6. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local stream is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+6. Click **Join** to start a call.
+
+
+ You see your connects to the from a restricted network environment using the cloud proxy service.
+
+
diff --git a/shared/signaling/geofencing/project-test/index.mdx b/shared/signaling/geofencing/project-test/index.mdx
new file mode 100644
index 000000000..c6e08dc30
--- /dev/null
+++ b/shared/signaling/geofencing/project-test/index.mdx
@@ -0,0 +1,28 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import ReactNative from './react-native.mdx';
+import Electron from './electron.mdx';
+import Flutter from './flutter.mdx';
+import MacOS from './macos.mdx'
+import Unity from './unity.mdx'
+import Windows from './windows.mdx';
+import Test from '@docs/shared/common/project-test/index.mdx';
+
+To test this functionality:
+
+
+
+4. **Test data encryption**
+
+ Login to as multiple users, then send and receive messages using geofencing.
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/geofencing/project-test/ios.mdx b/shared/signaling/geofencing/project-test/ios.mdx
new file mode 100644
index 000000000..f3011d0fa
--- /dev/null
+++ b/shared/signaling/geofencing/project-test/ios.mdx
@@ -0,0 +1,24 @@
+
+
+3. In the `ViewController`, update `appID`, `channelName`, and `token` with the values from .
+
+4. Run your using either a physical or a simulator iOS device.
+
+ If this is the first time you run the project, grant microphone and camera access to your .
+
+ If you use an iOS simulator, you see the remote video only. You cannot see the local video stream because of Apple simulator hardware restrictions.
+
+
+5. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+
+You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
+
diff --git a/shared/signaling/geofencing/project-test/macos.mdx b/shared/signaling/geofencing/project-test/macos.mdx
new file mode 100644
index 000000000..268fbc910
--- /dev/null
+++ b/shared/signaling/geofencing/project-test/macos.mdx
@@ -0,0 +1,20 @@
+
+
+3. In the `ViewController`, update `appID`, `channelName`, and `token` with the values from .
+
+4. Run your .
+
+
+5. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+
+You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
+
diff --git a/shared/signaling/geofencing/project-test/react-native.mdx b/shared/signaling/geofencing/project-test/react-native.mdx
new file mode 100644
index 000000000..ba94866cc
--- /dev/null
+++ b/shared/signaling/geofencing/project-test/react-native.mdx
@@ -0,0 +1,38 @@
+
+3. In `App.tsx`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+1. Run your app:
+
+ - *Android*
+
+ 1. Enable Developer options on your Android device, and then connect it to your computer using a USB cable.
+ 1. Run `npx react-native run-android` in the project root directory.
+
+ - *iOS:*
+
+ 1. Open the `ProjectName/ios/ProjectName.xcworkspace` folder with Xcode.
+ 1. Connect your iOS device to your Mac using a USB cable.
+ 1. Click the **Build and run** button in Xcode.
+
+
+ If this is the first time you run the project, you need to grant microphone and camera access to your .
+
+
+5. Select an option and click **Join** to start a session.
+ - When you join as a **Host**, the local video is published and played in the .
+ - When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+Now, you can see yourself on the device screen and talk to the remote user using your .
+
+
+
+5. Click **Join** to start a call.
+
+Now, you can talk to the remote user using your .
+
+
+
diff --git a/shared/signaling/geofencing/project-test/unity.mdx b/shared/signaling/geofencing/project-test/unity.mdx
new file mode 100644
index 000000000..1557aa689
--- /dev/null
+++ b/shared/signaling/geofencing/project-test/unity.mdx
@@ -0,0 +1,17 @@
+
+
+3. In your script file, update `_appID`, `_channelName` and `_token` with the values for your temporary token.
+
+4. In **Unity Editor**, click **Play**. A moment later you see the running on your development device.
+
+
+5. To join as a host, select **Host** and click **Join**.
+
+
+
+5. To connect to a channel, click **Join**.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
\ No newline at end of file
diff --git a/shared/signaling/geofencing/project-test/web.mdx b/shared/signaling/geofencing/project-test/web.mdx
new file mode 100644
index 000000000..5c2aa9d29
--- /dev/null
+++ b/shared/signaling/geofencing/project-test/web.mdx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/shared/signaling/geofencing/project-test/windows.mdx b/shared/signaling/geofencing/project-test/windows.mdx
new file mode 100644
index 000000000..22eade59e
--- /dev/null
+++ b/shared/signaling/geofencing/project-test/windows.mdx
@@ -0,0 +1,20 @@
+
+ 3. In `CAgoraImplementationDlg.h`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+ 4. In Visual Studio, click **Local Windows Debugger**. A moment later you see the project running on your development device.
+
+ If this is the first time you run the project, you need to grant microphone and camera access to your .
+
+
+ 5. Select an option and click **Join** to start a session.
+ - When you join as a **Host**, the local video is published and played in the .
+ - When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+ 5. Click **Join** to start a call. Now, you can see yourself on the device screen and talk to the remote user using your .
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
\ No newline at end of file
diff --git a/shared/signaling/geofencing/reference/index.mdx b/shared/signaling/geofencing/reference/index.mdx
index 1077dbc4f..304b89858 100644
--- a/shared/signaling/geofencing/reference/index.mdx
+++ b/shared/signaling/geofencing/reference/index.mdx
@@ -6,6 +6,75 @@ import Unity from './unity.mdx';
import Web from './web.mdx';
import Windows from './windows.mdx';
+### Firewall requirements
+
+If a firewall is deployed in your network environment, ensure that you add the domains in the following table according to the region you specify, allow all IP addresses, and open the following firewall ports.
+
+- Whitelist domains
+
+
-
-
-
-
-
-```
-
-### 2. Implement peer-to-peer and channel messaging
-
-Refer to the following code sample to modify the `index.js` file. Note that to avoid browser-compatibility issues, the sample uses the import command to import the SDK and webpack to package the JS file. You need to replace `""` and `""` with the App ID and token you have acquired in the previous step.
-
-```javascript
-import AgoraRTM from 'agora-rtm-sdk'
-
-// Params for login
-let options = {
- uid: "",
- token: ""
-}
-
-// Your app ID
-const appID = ""
-// Your token
-options.token = ""
-
-// Initialize client
-const client = AgoraRTM.createInstance(appID)
-
-// Client Event listeners
-// Display messages from peer
-client.on('MessageFromPeer', function (message, peerId) {
-
- document.getElementById("log").appendChild(document.createElement('div')).append("Message from: " + peerId + " Message: " + message)
-})
-// Display connection state changes
-client.on('ConnectionStateChanged', function (state, reason) {
-
- document.getElementById("log").appendChild(document.createElement('div')).append("State changed To: " + state + " Reason: " + reason)
-
-})
-
-let channel = client.createChannel("demoChannel")
-
-channel.on('ChannelMessage', function (message, memberId) {
-
- document.getElementById("log").appendChild(document.createElement('div')).append("Message received from: " + memberId + " Message: " + message)
-
-})
-// Display channel member stats
-channel.on('MemberJoined', function (memberId) {
-
- document.getElementById("log").appendChild(document.createElement('div')).append(memberId + " joined the channel")
-
-})
-// Display channel member stats
-channel.on('MemberLeft', function (memberId) {
-
- document.getElementById("log").appendChild(document.createElement('div')).append(memberId + " left the channel")
-
-})
-
-// Button behavior
-window.onload = function () {
-
- // Buttons
- // login
- document.getElementById("login").onclick = async function () {
- options.uid = document.getElementById("userID").value.toString()
- await client.login(options)
- }
-
- // logout
- document.getElementById("logout").onclick = async function () {
- await client.logout()
- }
-
- // create and join channel
- document.getElementById("join").onclick = async function () {
- // Channel event listeners
- // Display channel messages
- await channel.join().then (() => {
- document.getElementById("log").appendChild(document.createElement('div')).append("You have successfully joined channel " + channel.channelId)
- })
- }
-
- // leave channel
- document.getElementById("leave").onclick = async function () {
-
- if (channel != null) {
- await channel.leave()
- }
-
- else
- {
- console.log("Channel is empty")
- }
-
- }
-
- // send peer-to-peer message
- document.getElementById("send_peer_message").onclick = async function () {
-
- let peerId = document.getElementById("peerId").value.toString()
- let peerMessage = document.getElementById("peerMessage").value.toString()
-
- await client.sendMessageToPeer(
- { text: peerMessage },
- peerId,
- ).then(sendResult => {
- if (sendResult.hasPeerReceived) {
-
- document.getElementById("log").appendChild(document.createElement('div')).append("Message has been received by: " + peerId + " Message: " + peerMessage)
-
- } else {
-
- document.getElementById("log").appendChild(document.createElement('div')).append("Message sent to: " + peerId + " Message: " + peerMessage)
-
- }
- })
- }
-
- // send channel message
- document.getElementById("send_channel_message").onclick = async function () {
-
- let channelMessage = document.getElementById("channelMessage").value.toString()
-
- if (channel != null) {
- await channel.sendMessage({ text: channelMessage }).then(() => {
-
- document.getElementById("log").appendChild(document.createElement('div')).append("Channel message: " + channelMessage + " from " + channel.channelId)
-
- }
-
- )
- }
- }
-}
-```
-
-## Test your app
-
-This article uses [webpack](https://webpack.js.org/) to package the project and `webpack-dev-server` to run the project.
-
-1. In the `package.json` file, add `webpack`, `webpack-cli`, and `webpack-dev-server` to the `dependencies` field, and the `build` and `start:dev` commands to the `scripts` field.
-
- ```json
- {
- "name": "web",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "build": "webpack --config webpack.config.js",
- "start:dev": "webpack serve --config webpack.config.js"
- },
- "dependencies": {
- "agora-rtm-sdk": "latest",
- "webpack": "^5.28.0",
- "webpack-dev-server": "^4.0.0"
- },
- "author": "",
- "license": "ISC",
- "devDependencies": {
- "webpack-cli": "^5.1.1"
- }
- }
- ```
-
-2. Create a file named `webpack.config.js` in the project directory to configure webpack. The JS file should contain the following lines:
-
- ```javascript
- const path = require("path");
-
- module.exports = {
- entry: "./index.js",
- output: {
- filename: "bundle.js",
- path: path.resolve(__dirname, "./dist"),
- },
- devServer: {
- static: path.resolve(__dirname, "./"),
- compress: true,
- port: 9000,
- },
- };
- ```
-
- The project directory now has the following structure:
-
- ```text
- RTM_quickstart
- ├─ index.html
- ├─ index.js
- ├─ package.json
- └─ webpack.config.js
- ```
-
-3. Run the following command in the project directory to install dependencies:
-
- ```shell
- $ npm install
- ```
-
-4. Run the following command to build and run the project using webpack:
-
- ```shell
- # Use webpack to package the project
- $ npm run build
- ```
-
-5. Use webpack-dev-server to run the project
-
- ```shell
- $ npm run start:dev
- ```
-
-If the project runs successfully, you can send and receive peer-to-peer messages and channel messages.
-
-![1618225131946](https://web-cdn.agora.io/docs-files/1618225131946)
-
-## Considerations
-
-- The Agora supports creating multiple RtmClient instances that are independent of each other.
-
-- To send and receive peer-to-peer or channel messages, ensure that you have successfully logged in the Agora .
-
-- To use any of the channel features, you must first call the createChannel method to create a channel instance.
-
-- You can create multiple channel instances for each RtmClient instance, but you can only join a maximum of 20 channels at the same time. The `channelId` parameter needs to be channel-specific.
-
-- When you do not want to use a specific instance any more, you can use the `removeAllListeners` method to remove all its listeners.
-
-- You cannot reuse a received RtmMessage instance.
-
-## Next steps
-
-Generating a token by hand is not helpful in a production context. [Authenticate Your Users with Tokens](../develop/authentication-workflow) shows you how to start Signaling with a token that you retrieve from your server.
-
-## See also
-
-### Sample project
-
-Agora provides an open-source [sample project](https://github.com/AgoraIO/Signaling/tree/master/Agora-Signaling-Tutorial-Web) on GitHub for your reference.
-
-### SDK Integration Methods
-
-[Downloads](../reference/downloads) shows you alternative ways to add in your project.
-
-
diff --git a/shared/signaling/get-started-sdk/windows.mdx b/shared/signaling/get-started-sdk/windows.mdx
deleted file mode 100644
index f32c783f1..000000000
--- a/shared/signaling/get-started-sdk/windows.mdx
+++ /dev/null
@@ -1,401 +0,0 @@
-
-
-
-
-## Project setup
-
-### Create a C++ console app
-
-[Create a C++ console app with Visual Studio](https://docs.microsoft.com/en-us/cpp/build/vscpp-step-1-create?view=msvc-150).
-
-- Select **Visual C++ > Console App**.
-- Set **Name** and the **Solution name** as `RtmQuickstart`.
-
-### Integrate the SDK
-
-Complete the following steps to integrate the Agora into your project.
-
-1. Configure the project files.
-
-- Go to [SDK Downloads](/sdks), download the latest version of the C++ SDK for Windows, and unzip the downloaded SDK package.
-- Copy all subfolders of the **sdk** folder of the downloaded SDK package to the solution directory. Ensure that the subfolders are in the same location as your **.sln** file.
-
-2. Configure the project properties.
-
-Right-click the project name in the **Solution Explorer** window, click **Properties** to configure the following project properties, and click **OK**.
-
-- Go to the **C/C++ > General > Additional Include Directories** menu, click **Edit**, and input **$(SolutionDir)include** in the pop-up window.
-- Go to the **Linker > General > Additional Library Directories** menu, click **Edit**, and input **$(SolutionDir)lib** in the pop-up window.
-- Go to the **Linker > Input > Additional Dependencies** menu, click **Edit**, and input **agora_rtm_sdk.lib** in the pop-up window.
-
-## Implement real-time messaging
-
-Open `RtmQuickstart.cpp` and replace the file content with the following code. You need to replace `` with your App ID; replace `` with your token.
-
-```cpp
-// RtmQuickstart.cpp : This file contains the 'main' function. Program execution begins and ends there.
-//
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
diff --git a/shared/signaling/limitations/index.mdx b/shared/signaling/limitations/index.mdx
index 668b0a615..8fce423be 100644
--- a/shared/signaling/limitations/index.mdx
+++ b/shared/signaling/limitations/index.mdx
@@ -6,7 +6,95 @@ import Unity from './unity.mdx';
import Web from './web.mdx';
import Windows from './windows.mdx';
+## API call limit
+Unless otherwise specified below, limit the API call frequency by a single client to 20 calls per second. If the number of calls per second exceeds 20, some calls are ignored by the SDK. If you need to call more APIs per second, contact [rtm@agora.io](mailto:rtm@agora.io).
+
+### General
+
+| Item | Soft limit | Hard limit | Comments |
+|:---:|:---:|:---:|:---:|
+| Number of projects in an account | 1000 | N/A | To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Number of client instances | 1 | 1 | Attempts to create multiple instances fail. |
+| Number of message channels per app ID | Unlimited | N/A | Channel resources are public, users can take them as needed. |
+| Number of stream channels per app ID | Unlimited | N/A | Channel resources are public, users can take them as needed. |
+| User ID length | 64 ASCII characters | 64 ASCII characters | Exceeding the soft limit produces an error. |
+
+### Message channel
+
+| Item | Soft limit | Hard limit | Comments |
+|:---:|:---:|:---:|:---:|
+| Number of messages per second in a channel | 30 messages/sec | 60 messages/sec | Exceeding the soft limit produces an error. To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Number of subscribers per channel | Unlimited | N/A | |
+| Number of publishers per channel | Unlimited | N/A | |
+| Number of subscribed channels per client | 50 | N/A | Exceeding the soft limit produces an error. |
+| Message packet size | 32 KB | 32 KB | Exceeding the soft limit produces an error. To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Channel name length | 64 ASCII characters | 64 ASCII characters | Exceeding the soft limit produces an error. |
+| Custom message type length | 32 ASCII characters | 32 ASCII characters | Exceeding the soft limit produces an error. |
+
+### Stream channel
+
+| Item | Soft limit | Hard limit | Comments |
+|:---:|:---:|:---:|:---:|
+| Number of created channels per client | Unlimited | N/A | |
+| Number of joined channels per client | 100 | N/A | Exceeding the soft limit produces an error. To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Number of users per channel | 1000 | N/A | Exceeding the soft limit produces an error. To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Number of topics per channel | Unlimited | N/A | |
+| Channel name length | 64 ASCII characters | 64 ASCII characters | Exceeding the soft limit produces an error. |
+
+### Topic
+
+| Item | Soft limit | Hard limit | Comments |
+|:---:|:---:|:---:|:---:|
+| Number of topics joined by a client in a channel | 8 | 8 | Exceeding the soft limit produces an error. |
+| Number of messages published by a client in a topic per second | 120 messages/sec | 200 messages/sec | Exceeding the soft limit produces an error. To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io).|
+| Number of topics subscribed to by a client in a channel | 50 | 50 | Exceeding the soft limit produces an error. |
+| Number of users subscribed to a topic by a client | 64 | 64 | A single client can subscribe up to 64 user IDs to a single topic. Exceeding the soft limit produces an error. |
+| Number of publishers in a topic | Unlimited | N/A | |
+| Topic name length | 8 ASCII characters | 8 ASCII characters | The topic name length will be extended to 64 ASCII characters in future releases. |
+| Message packet size | 32 KB | 32 KB | Exceeding the soft limit produces an error. To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Custom message type length | 32 ASCII characters | 32 ASCII characters | Exceeding the soft limit produces an error. |
+
+### Presence
+
+| Item | Soft limit | Hard limit | Comments |
+|:---:|:---:|:---:|:---:|
+| Presence timeout | 10 seconds - 300 seconds. Defaults to 300 seconds. | N/A | When the set timeout value is exceeded, the SDK matches the closest boundary value. For example, if you set presence timeout to 400 seconds, the actual application time is 300 seconds. To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Number of temporary user state key/value pairs | 32 pairs | 32 pairs | Exceeding the soft limit produces an error. |
+| Number of temporary user states cached before joining the channel | 100 | 100 | Exceeding the soft limit produces an error. |
+
+### Storage
+
+| Item | Soft limit | Hard limit | Comments |
+|:---:|:---:|:---:|:---:|
+| Metadata item key length | 32 ASCII characters | N/A | |
+| Number of metadata sets per user or channel | 1 | 1 | Each channel and each user can only have one set of channel or user metadata, respectively. |
+| Number of items per channel metadata or user metadata set | Unlimited | N/A | |
+| Storage space per channel or user metadata set | 64KB | N/A | To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Storage space per metadata item | 64KB | N/A | To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Channel metadata API call frequency by a single client | 10 times/sec | 20 times/sec | To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| User metadata API call frequency by a single client | 10 times/sec | 20 times/sec | To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Number of channel metadata sets per app ID | 1 million | N/A | |
+| Number of user metadata sets per app ID | 1 million | N/A | To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+
+### Lock
+
+| Item | Soft limit | Hard limit | Comments |
+|:---:|:---:|:---:|:---:|
+| Number of locks per channel | 32 | N/A | To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Number of locked locks per client | Unlimited | N/A | |
+| Lock TTL (Time to live) | 10 seconds - 300 seconds | 10 seconds - 300 seconds | When a user leaves a channel or disconnects, the lock held by that user is automatically released after the set time. When the set value is exceeded, the SDK matches the closest boundary value. For example, if you set lock TTL to 400 seconds, the actual application time is 300 seconds. |
+| Lock length | 64 ASCII characters | 64 ASCII characters | Exceeding the soft limit produces an error. |
+| Number of locks per app ID | 1 million | N/A | Exceeding the soft limit produces an error. To change the limit, contact [rtm@agora.io](mailto:rtm@agora.io). |
+| Lock acquisition API call frequency | 10 times/sec | N/A | |
+
+## String size
+
+The maximum size of the response in a call invitation is 8 KB.
+
+## Unicode support
+
+Supports channel and peer-to-peer messages, invitation content, and invitation response in UTF-8 only.
diff --git a/shared/signaling/limitations/web.mdx b/shared/signaling/limitations/web.mdx
index 5270990a6..3c97a67b5 100644
--- a/shared/signaling/limitations/web.mdx
+++ b/shared/signaling/limitations/web.mdx
@@ -1,40 +1,4 @@
-## API call limit
-
-The call limit is for one RtmClient instance. If an operation corresponds to multiple methods, the number of the method calls of an operation equals the sum of the method calls of all corresponding methods in a specific time frame.
-
-
We do not recommend increasing the call limit by creating multiple RtmClient instances.
-
-| Function | Method | Call limit |
-| ----------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------ |
-| Log in to the Agora | login | Two calls per second |
-| Retrieve member count of specified channels | getChannelMemberCount | One call per second |
-| Join the same channel each time1 | join | 50 calls every three seconds |
-| Join a different channel each time1 | join | Two calls every five seconds |
-| Send messages |
sendMessageToPeer
sendMessage
| 180 calls every three seconds |
-| Retrieve a member list of the channel | getMembers | Five calls every two seconds |
-| Renew the Token | renewToken | Two calls per second |
-| Query the online status of the specified users | queryPeersOnlineStatus | 10 calls every five seconds |
-| Set user attributes |
setLocalUserAttributes
addOrUpdateLocalUserAttributes
deleteLocalUserAttributesByKeys
clearLocalUserAttributes
| 10 calls every five seconds |
-| Get user attributes |
getUserAttributes
getUserAttributesByKeys
| 40 calls every five seconds |
-| Set channel attributes |
setChannelAttributes
addOrUpdateChannelAttributes
deleteChannelAttributesByKeys
clearChannelAttributes
| 10 calls every five seconds |
-| Get channel attributes |
getChannelAttributes
getChannelAttributesByKeys
| 10 calls every five seconds |
-
-
-## String size
-
-- The maximum size of a peer-to-peer or channel message is 32 KB. See RtmMessage.text.
-- The maximum size of the response in a call invitation is 8 KB. See RemoteInvitation.response
-
-## Supported browsers
-
-See [Supported Browsers](../overview/supported-platforms).
-
-## Unicode support
-
-Supports channel and peer-to-peer messages, invitation content, and invitation response in UTF-8 only.
-
-
\ No newline at end of file
diff --git a/shared/signaling/metadata-sdk.mdx b/shared/signaling/metadata-sdk.mdx
new file mode 100644
index 000000000..cab5d8ca9
--- /dev/null
+++ b/shared/signaling/metadata-sdk.mdx
@@ -0,0 +1,19 @@
+---
+title: 'Metadata SDK'
+sidebar_position: 8
+type: docs
+description: >
+ Easy-to-use, serverless storage for user and channel data
+---
+import MetadataAPI from '@docs/shared/signaling/metadata-sdk/index.mdx';
+
+export const toc = [{}]
+
+Metadata provides easy-to-use, serverless storage for user and channel data you need to build innovative, reliable,
+scalable applications. Use Metadata SDK to easily store metadata about the users and channels in your app without
+setting up your own databases.
+
+Signalling triggers events when metadata is set or removed. Your app can receive these events in real
+time in order to update the front-end accordingly.
+
+
diff --git a/shared/signaling/metadata-sdk/android.mdx b/shared/signaling/metadata-sdk/android.mdx
new file mode 100644
index 000000000..416cbbed7
--- /dev/null
+++ b/shared/signaling/metadata-sdk/android.mdx
@@ -0,0 +1,753 @@
+
+
+
+## User metadata
+
+Creates an `RtmMetadataItem` instance.
+
+**Method**
+
+```javascript
+ public class RtmMetadataItem
+```
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("mode", "emo");
+```
+
+**Response**
+
+Returns a `RtmMetadataItem` instance. `RtmMetadataItem` is a basic unit item of a `UserMetadata` and
+ `ChannelMetadata`. It contains the following properties:
+
+| Property Name | Type | Description |
+| --------------------------------- | -------- | ------------------------------------------------------------------- |
+| `void setKey(String key)` | function | Set key for current `RtmMetadataItem` |
+| `String getKey()` | function | Get key for current `RtmMetadataItem` |
+| `void setValue(String value)` | function | Set value for current `RtmMetadataItem` |
+| `String getValue()` | function | Get value for current `RtmMetadataItem` |
+| `void setRevision(long revision)` | function | Set revision for current `RtmMetadataItem` |
+| `long getRevision()` | function | Get revision for current `RtmMetadataItem` |
+| `long getLastUpdateTs()` | function | Get updated time for current `RtmMetadataItem` |
+| `String getAuthorUserId()` | function | Get the uid of who update this record for current `RtmMetadataItem` |
+
+#### Get user metadata
+
+
+
+Gets all metadataItems of a specified user.
+
+**Method**
+
+```java
+void getUserMetadata(String userId, ResultCallback resultCallback)
+```
+
+| Parameter | Type | Required | Default | Description |
+|:----------------:|:--------:|:--------:|:-------:|:-----------------------:|
+| `userId` | String | yes | | Unique user identifier. |
+| `resultCallback` | function | yes | | callback function |
+
+**Basic Use**
+
+```java
+mRtmClient.getUserMetadata("Tony", new ResultCallback(){
+ @Override
+ public void onSuccess(RtmMetadata userMetadata){
+ // process success result!
+ }
+ @Override
+ public void onFailure(int errorInfo){
+ // process failure result!
+ }
+});
+```
+
+**Response**
+
+Returns a type `RtmMetadata` for the specific `uid` which contains the following properties:
+
+| Property Name | Type | Description |
+|:---------------:|:------------------------:|:------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | `List` | `RtmMetadataItem` type array which contains a single Key-Value metadata item for user, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `majorRevision` | number | The major revision for this user metadata |
+
+
+
+
+
+
+#### Set user metadata
+
+
+
+Set the local user’s metadata.
+
+**Method**
+
+```java
+void setLocalUserMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:------------------------:|:------------------------------------------------------------------------------------------------------------------------------ |
+| `items` | `List` | `RtmMetadataItem` type list which contains a single Key-Value metadata item for user, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+
+`RtmMetadataOptions` is a set of optional properties for operations, it contains the following properties:
+
+| Property Name | Type | Description |
+| -------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `majorRevision` | number | Revison control parametes. when the `majorRevision` you supplied is as same as the one in the storage, this operation will success. |
+| `enableRecordTs` | bool | When it is set to `true`, the final `RtmMetadataItem` will record the updating time automatically |
+| `enableRecordUserId` | bool | When it is set to `true`, the final `RtmMetadataItem` will record who have updated this item automatically |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("mode", "emo");
+RtmMetadataItem item2 = new RtmMetadataItem("gender", "male");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmClient.setLocalUserMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "setLocalUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "setLocalUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> caution: This operation will reset all current meatadata and set a new one.
+
+#### Add user metadata
+
+
+
+Adds metadata items to local user’s metadata.
+
+**Method**
+
+```javascript
+void addLocalUserMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback);
+```
+
+| Property Name | Type | Description |
+|:----------------:|:-------------------------:|:------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | List\< `RtmMetadataItem` > | `RtmMetadataItem` type array which contains a single Key-Value metadata item for user, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("mode", "emo");
+RtmMetadataItem item2 = new RtmMetadataItem("gender", "male");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmClient.addLocalUserMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "addLocalUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "addLocalUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution: This operation will add new metadata items for current user. It will report errors if the key of new `RtmMetadataItem` has already existed in the user metadata.
+
+#### Clear user metadata
+
+
+
+Delete all the local user’s metadata items.
+
+**Method**
+
+```java
+void clearLocalUserMetadata(RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:--------------------:|:--------------------------------------------------------------------------------------------------- |
+| `options` | `RtmMetadataOptions` | add optional propeties for current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmClient.clearLocalUserMetadata(options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "clearLocalUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "clearLocalUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution: This operation will clean all user metadata.
+
+#### Update user metadata
+
+
+
+Update the local user’s metadata items.
+
+**Method**
+
+```java
+void updateLocalUserMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:------------------------:|:------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | `List`| `RtmMetadataItem` type array which contains a single Key-Value metadata item for user, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("mode", "emo");
+RtmMetadataItem item2 = new RtmMetadataItem("gender", "male");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmClient.updateLocalUserMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "updateLocalUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "updateLocalUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution : This operation can only valid for updating the existing metadata items, or it will report errors.
+
+#### Delete user metadata
+
+
+
+Delete the local user’s metadata items.
+
+**Method**
+
+```java
+void deleteLocalUserMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:-------------------------:|:------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | List\< `RtmMetadataItem` > | `RtmMetadataItem` type array which contains a single Key-Value metadata item for user, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("mode", "");
+RtmMetadataItem item2 = new RtmMetadataItem("gender", "");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmClient.deleteLocalUserMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "deleteLocalUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "deleteLocalUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution:This operation will always excute regardless of the existing of metadata items
+
+#### Subscribe user metadata
+
+
+
+Subscribe to user metadata update events for a specific user.
+
+**Method**
+
+```java
+void subscribeUserMetadata(String userId, ResultCallback resultCallback);
+```
+
+| Parameter | Type | Required | Default | Description |
+|:----------------:|:--------:|:--------:|:-------:|:-----------------------:|
+| `userid` | String | yes | | Unique user identifier. |
+| `resultCallback` | function | yes | | callback function |
+
+**Basic Use**
+
+```java
+mRtmClient.subscribeUserMetadata("Tony", new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "subscribeUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "subscribeUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+#### Unsubscribe user metadata
+
+
+
+Unsubscribe to user metadata update events for a specific user.
+
+**Method**
+
+```java
+void unsubscribeUserMetadata(String userId, ResultCallback resultCallback)
+```
+
+| Parameter | Type | Required | Default | Description |
+|:----------------:|:--------:|:--------:|:-------:|:-----------------------:|
+| `userId` | String | yes | | Unique user identifier. |
+| `resultCallback` | function | yese | | callback function |
+
+**Basic Use**
+
+```java
+mRtmClient.unsubscribeUserMetadata("Tony", new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "unsubscribeUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "unsubscribeUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+#### User metadata event
+
+
+
+it will occur when user's metadata are updated(`Add`/`Set`/`Clear`/`Update`/`Delete`), You need to complete this procedure yourself, and then you can handle this event when you are subscribing user's metadata.
+
+**Method**
+
+```java
+void onUserMetadataUpdated(String userId, RtmMetadata data)
+```
+
+**Basic Use**
+
+```java
+class RtmClientListener implements RtmClientListener {
+ //..
+ @Override
+ public void onUserMetadataUpdated(String userId, RtmMetadata data) {
+ visualLog_CALLBACK("onUserMetadataUpdated, userId: " + userId);
+ for (RtmMetadataItem item: data.items) {
+ visualLog("Item key: " + item.getKey() + ", value: " + item.getValue()
+ + ", revision: " + item.getRevision() + ", ts: " + item.getLastUpdateTs()
+ + ", uid: " + item.getAuthorUserId());
+ }
+ }
+ //..
+}
+
+rtmClient = RtmClient.createInstance(context, appId, new RtmClientListener());
+```
+
+**Response**
+
+When this event occurs, you can recieve a uid which indicating whose metadata have changed and a type `RtmMetadata` for the user which contains the following properties:
+
+| Property Name | Type | Description |
+|:---------------:|:--------------------------:| -------------------------------------------------------------------------------- |
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single Key-Value metadata item for user |
+| `majorRevision` | number | The major revision for this user metadata |
+
+> **Caution**: It should be noted that the returned data contains the full amount of data of the current user, `Add`/`Set`/`Clear`/`Update`/`Delete` operation all will trigger this event, and you cannot distinguish which operation caused the current event. Need more features, you can use our new version 2.1.
+
+## Channel metadata
+
+#### Set channel metadata
+
+
+
+set the metadata of the channel.
+
+**Method**
+
+```java
+void setChannelMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:-----------------------:|:---------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | List\<`RtmMetadataItem`> | `RtmMetadataItem` type array which contains a single Key-Value metadata item for channel, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+The `RtmMetadataOptions` is a set of optional properties for operations, it contains the following properties:
+
+| Property Name | Type | Description |
+| -------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `majorRevision` | number | Revison control parametes. when the `majorRevision` you supplied is as same as the one in the storage, this operation will success. |
+| `enableRecordTs` | bool | When it is set to `true`, the final `RtmMetadataItem` will record the updating time automatically |
+| `enableRecordUserId` | bool | When it is set to `true`, the final `RtmMetadataItem` will record who have updated this item automatically |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("Announcement", "Welcome to RTM");
+RtmMetadataItem item2 = new RtmMetadataItem("Channel_type", "Public");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmChannel.setChannelMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "setChannelMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "setChannelMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> caution: This operation will `reset` all current meatadata of channel and set a new one for channel.
+
+#### Add channel metadata
+
+
+
+Add new metadata items to the channel.
+
+**Method**
+
+```java
+void addChannelMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:-------------------------:|:---------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | List\<(`RtmMetadataItem` > | `RtmMetadataItem` type array which contains a single Key-Value metadata item for channel, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("Announcement", "Welcome to RTM");
+RtmMetadataItem item2 = new RtmMetadataItem("Channel_type", "Public");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmChannel.addChannelMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "addChannelMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "addChannelMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution: This operation will add new metadata items for current channel. It will report errors if the key of new `RtmMetadataItem` has already existed in the channel metadata.
+
+#### Clear channel metadata
+
+
+
+delete all metadata items of the channel.
+
+**Method**
+
+```java
+clearChannelMetadata(RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:--------------------:|:------------------------------------------------------------------------------------------------------ |
+| `options` | `RtmMetadataOptions` | add optional propeties for current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmChannel.clearChannelMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "clearChannelMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "clearChannelMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution: This operation will clean all metadata of the specific channel.
+
+#### Update channel metadata
+
+
+
+Update metadata items of the channel.
+
+**Method**
+
+```java
+updateChannelMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:------------------------:|:---------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | `List`| `RtmMetadataItem` type array which contains a single Key-Value metadata item for channel, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("Announcement", "Welcome to RTM");
+RtmMetadataItem item2 = new RtmMetadataItem("Channel_type", "Public");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmChannel.updateChannelMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "updateChannelMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "updateChannelMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution : This operation can only valid for updating the existing metadata items, or it will report errors.
+
+#### Delete channel metadata
+
+
+
+delete metadata items of the channel.
+
+**Method**
+
+```java
+void deleteChannelMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:-----------------------:|:---------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | `List` | `RtmMetadataItem` type array which contains a single Key-Value metadata item for channel, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("Announcement", "");
+RtmMetadataItem item2 = new RtmMetadataItem("Channel_type", "");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmChannel.deleteChannelMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "deleteChannelMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "deleteChannelMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution:This operation will always excute regardless of the existing of metadata items
+
+#### Get channel metadata
+
+
+
+get all metadata items of the channel.
+
+**Method**
+
+```javascript
+getChannelMetadata(ResultCallback resultCallback)
+```
+
+| Parameter | Type | Required | Default | Description |
+|:----------------:|:--------:|:--------:|:-------:|:-----------------:|
+| `resultCallback` | function | yes | | callback function |
+
+**Basic Use**
+
+```java
+mRtmChannel.getChannelMetadata( new ResultCallback(){
+ @Override
+ public void onSuccess(RtmMetadata channelMetadata){
+ // process success result!
+ }
+ @Override
+ public void onFailure(int errorInfo){
+ // process failure result!
+ }
+});
+```
+
+**Response**
+
+Returns a type `RtmMetadata` for the specific `channel` which contains the following properties:
+
+| Property Name | Type | Description |
+| --------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | List< `RtmMetadataItem` > | `RtmMetadataItem` type array which contains a single Key-Value metadatafor channel, see `[RtmMetadataIte](1)` for more information |
+| `majorRevision` | number | The major revision for this channel metadata |
+
+> **Caution**: If the specific `channel` have not set any user metadata yet, this operation will return a empty {}.
+
+#### Listen channel metadata update event
+
+When you join channel ,you will automatically Listen the Channel metadata Update Event
+
+#### Channel metadata event
+
+This occurs when channel's metadata are updated, You need to complete this procedure yourself, and then you can handle
+ this event when you are in the channel
+
+**Method**
+
+```javascript
+void onMetadataUpdated(RtmMetadata data)
+```
+
+**Basic Use**
+
+```java
+private RtmChannelListener rtmChannelListener = new RtmChannelListener(){
+
+ @Override
+ public void onMetadataUpdated(RtmMetadata data) {
+ visualLog_CALLBACK("onMetadataUpdated");
+ for (RtmMetadataItem item: data.items) {
+ visualLog( "Item key: " + item.getKey() + ", value: " + item.getValue()
+ + ", revision: " + item.getRevision() + ", ts: " + item.getLastUpdateTs()
+ + ", userId: " + item.getAuthorUserId());
+ }
+ }
+};
+
+RtmChannel rtmChannel = mRtmClient.createChannel(channelId.toString(), rtmChannelListener);
+```
+
+**Response**
+
+When this event occurs, you can recieve a type `RtmMetadata` for the specific `channel` which contains the following properties:
+
+| Property Name | Type | Description |
+|:---------------:|:-------------------------:| ----------------------------------------------------------------------------------- |
+| `items` | List\< `RtmMetadataItem` > | `RtmMetadataItem` type array which contains a single Key-Value metadata itemfor channel |
+| `majorRevision` | number | The major revision for this channel metadata |
+
+> **Caution**: It should be noted that the returned data contains the full amount of data of the current channel, `Add`/`Set`/`Clear`/`Update`/`Delete` operation all will trigger this event, and you cannot distinguish which operation caused the current event. Need more features, you can use our new version 2.1.
+
+
diff --git a/shared/signaling/metadata-sdk/index.mdx b/shared/signaling/metadata-sdk/index.mdx
new file mode 100644
index 000000000..1077dbc4f
--- /dev/null
+++ b/shared/signaling/metadata-sdk/index.mdx
@@ -0,0 +1,15 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Linux from './linux.mdx';
+import Macos from './macos.mdx';
+import Unity from './unity.mdx';
+import Web from './web.mdx';
+import Windows from './windows.mdx';
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/metadata-sdk/ios.mdx b/shared/signaling/metadata-sdk/ios.mdx
new file mode 100644
index 000000000..b7e1a68c5
--- /dev/null
+++ b/shared/signaling/metadata-sdk/ios.mdx
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/metadata-sdk/linux.mdx b/shared/signaling/metadata-sdk/linux.mdx
new file mode 100644
index 000000000..f665e017e
--- /dev/null
+++ b/shared/signaling/metadata-sdk/linux.mdx
@@ -0,0 +1,3 @@
+
+Metadata SDK is not currently available for this platform.
+
diff --git a/shared/signaling/metadata-sdk/macos.mdx b/shared/signaling/metadata-sdk/macos.mdx
new file mode 100644
index 000000000..c57b76049
--- /dev/null
+++ b/shared/signaling/metadata-sdk/macos.mdx
@@ -0,0 +1,4 @@
+
+
+ Metadata SDK is not currently available for this platform.
+
diff --git a/shared/signaling/metadata-sdk/unity.mdx b/shared/signaling/metadata-sdk/unity.mdx
new file mode 100644
index 000000000..f5a2d874f
--- /dev/null
+++ b/shared/signaling/metadata-sdk/unity.mdx
@@ -0,0 +1,4 @@
+
+
+ Metadata SDK is not currently available for this platform.
+
diff --git a/shared/signaling/metadata-sdk/web.mdx b/shared/signaling/metadata-sdk/web.mdx
new file mode 100644
index 000000000..520f742c5
--- /dev/null
+++ b/shared/signaling/metadata-sdk/web.mdx
@@ -0,0 +1,806 @@
+
+
+
+### User metadata
+
+Creates an `RtmMetadataItem` instance.
+
+**Method**
+
+```javascript
+ createMetadataItem(): RtmMetadataItem;
+```
+
+**Basic Use**
+
+```javascript
+const item1 = createMetadataItem();
+const item2 = createMetadataItem();
+item1.setKey("mode");
+item1.setValue("mode");
+item2.SetKey("gender");
+item2.setValue("male");
+```
+
+**Response**
+
+Returns a `RtmMetadataItem` instance. `RtmMetadataItem` is a basic unit item of `UserMetadata` and `ChannelMetadata`.
+ It contains the following properties:
+
+| Property Name | Type | Description |
+| -------------------------------------- | -------- | ------------------------------------------------------------------- |
+| `setKey(key: string): void` | function | Set key for the current `RtmMetadataItem` |
+| `getKey(): string` | function | Get key for the current `RtmMetadataItem` |
+| `setValue(value: string\| null): void` | function | Set value for the current `RtmMetadataItem` |
+| `getValue(): string\| null` | function | Get the value for the current `RtmMetadataItem` |
+| `setRevision(revision: number): void` | function | Set revision for the current `RtmMetadataItem` |
+| `getRevision(): number` | function | Get revision for the current `RtmMetadataItem` |
+| `getUpdateTs(): number` | function | Get updated time for the current `RtmMetadataItem` |
+| `getAuthorUserId(): string` | function | Get the uid of who update this record for the current `RtmMetadataItem` |
+
+#### Get user metadata
+
+Get the metadata for a specified user.
+
+**Method**
+
+```javascript
+getUserMetadata(uid: string): Promise;
+```
+
+| Parameter | Type | Required | Default | Description |
+|:---------:|:------:|:--------:|:-------:|:-----------------------:|
+| uid | string | yes | | Unique user identifier. |
+
+**Basic Use**
+
+```javascript
+try{
+ const rtmMetadata = await getUserMetadata("Tony");
+ console.log(rtmMetadata);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+Returns a `RtmMetadata` object for the specific `uid`. The returned object contains the following properties:
+
+| Property Name | Type | Description |
+|:---------------:|:--------------------------:|:--------------------------------------------------------------------------------:|
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single Key-Value metadata item for a user |
+| `majorRevision` | number | The major revision for this user metadata |
+
+The `RtmMetadataItem` is a basic unit item of a UserMetadata, it contains the following properties:
+
+| Property Name | Type | Description |
+|:---------------------------------------:|:--------:|:-------------------------------------------------------------------:|
+| `setKey(key: string): void` | function | Set key for the current `RtmMetadataItem` |
+| `getKey(): string` | function | Get key for the current `RtmMetadataItem` |
+| `setValue(value: string \| null): void` | function | Set value for the current `RtmMetadataItem` |
+| `getValue(): string \| null` | function | Get the value for the current `RtmMetadataItem` |
+| `setRevision(revision: number): void` | function | Set revision for the current `RtmMetadataItem` |
+| `getRevision(): number` | function | Get revision for the current `RtmMetadataItem` |
+| `getUpdateTs(): number` | function | Get updated time for the current `RtmMetadataItem` |
+| `getAuthorUserId(): string` | function | Get the uid of who update this record for the current `RtmMetadataItem` |
+
+If no user metadata is set for the specific `uid`, this operation returns an empty {}.
+
+#### Set user metadata
+
+Set the local user’s metadata.
+
+**Method**
+
+```javascript
+setLocalUserMetadata(
+ items: RtmMetadataItem[],
+ options?: RtmMetadataOptions
+ ): Promise;
+```
+
+| Property Name | Type | Description |
+|:-------------:|:-------------:|:-------------:|
+|`items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single key-value metadata item
+ for a user,see [`RtmMetadataItem`](#RtmMetadataItem) for more information. |
+| `options` | `RtmMetadataOptions` | Add optional properties to the current user metadata, see
+ [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+
+
+The `RtmMetadataOptions` is a set of optional properties for operations, it contains the following properties:
+
+| Property Name | Type | Description |
+| -------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `majorRevision` | number | Revison control parametes. when the `majorRevision` you supplied is as same as the one in the storage, this operation will success. |
+| `enableRecordTs` | bool | When it is set to `true`, the final `RtmMetadataItem` records the updating time
+ automatically |
+| `enableRecordUserId` | bool | When it is set to `true`, the final `RtmMetadataItem` records who have updated
+ this item automatically |
+
+
+**Basic Use**
+
+```javascript
+const item1 = createMetadataItem();
+const item2 = createMetadataItem();
+item1.setKey("mode");
+item1.setValue("mode");
+item2.SetKey("gender");
+item2.setValue("male");
+cosnt metadataOption = {
+ majorRevision: 1234567;
+ enableRecordTs: true;
+ enableRecordUserId: true;
+};
+
+try{
+ await setLocalUserMetadata([item1,item2],metadataOption);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+> caution: This operation will reset all current metadata and set a new one.
+
+#### Add user metadata
+
+Adds metadata items to the local user’s metadata.
+
+**Method**
+
+```javascript
+addLocalUserMetadata(
+ items: RtmMetadataItem[],
+ options?: RtmMetadataOptions
+ ): Promise;
+```
+
+
+| Property Name | Type | Description |
+|:-------------:|:--------------------------:|:-------------------------------------------------------------------------------------------------------------------------------:|
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single key-value metadata item for a user,see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | Add optional properties to the current user metadata, see
+ [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+
+
+**Basic Use**
+
+```javascript
+const item1 = createMetadataItem();
+const item2 = createMetadataItem();
+item1.setKey("mode");
+item1.setValue("mode");
+item2.SetKey("gender");
+item2.setValue("male");
+cosnt metadataOption = {
+ majorRevision: 1234567;
+ enableRecordTs: true;
+ enableRecordUserId: true;
+};
+
+try{
+ await addLocalUserMetadata([item1,item2],metadataOption);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+> Caution: This operation adds new metadata items for the current user. Reports errors if the key of new
+ `RtmMetadataItem` has already existed in the user metadata.
+
+#### Clear user metadata
+
+Delete all the local user’s metadata items.
+
+Method
+
+```javascript
+clearLocalUserMetadata(options?: RtmMetadataOptions): Promise;
+```
+
+| Property Name | Type | Description |
+|:-------------:|:--------------------:|:---------------------------------------------------------------------------------------------------:|
+| `options` | `RtmMetadataOptions` | Add optional properties to the current user metadata, see
+ [`RtmMetadataOptions`](#RtmMetadataOptions)for more information |
+
+**Basic Use**
+
+```javascript
+cosnt metadataOption = {
+ majorRevision: 1234567;
+};
+
+try{
+ await clearLocalUserMetadata(metadataOption);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+> Caution: This operation will clean all user metadata.
+
+#### Update user metadata
+
+
+
+Update the local user’s metadata items.
+
+Method
+
+```javascript
+updateLocalUserMetadata(
+ items: RtmMetadataItem[],
+ options?: RtmMetadataOptions
+ ): Promise;
+```
+
+| Property Name | Type | Description |
+|:-------------:|:--------------------------:|:-------------------------------------------------------------------------------------------------------------------------------:|
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single key-value metadata item for a user,see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | Add optional properties to the current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions)for more information |
+
+**Basic Use**
+
+```javascript
+const item1 = createMetadataItem();
+const item2 = createMetadataItem();
+item1.setKey("mode");
+item1.setValue("mode");
+item2.SetKey("gender");
+item2.setValue("male");
+cosnt metadataOption = {
+ majorRevision: 1234567;
+ enableRecordTs: true;
+ enableRecordUserId: true;
+};
+
+try{
+ await updateLocalUserMetadata([item1,item2],metadataOption);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+> Caution : This operation can only valid for updating the existing metadata items, or it will report errors.
+
+#### Delete user metadata
+
+
+
+Delete the local user’s metadata items.
+
+**Method**
+
+```javascript
+deleteLocalUserMetadata(
+ items: RtmMetadataItem[],
+ options?: RtmMetadataOptions
+ ): Promise;
+```
+
+| Property Name | Type | Description |
+|:-------------:|:--------------------------:|:-------------------------------------------------------------------------------------------------------------------------------:|
+| `items` | Array( `RtmMetadataItem` ) | An `RtmMetadataItem` type array which contains a single key-value
+ metadata item for a user,see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | Add optional properties to the current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions)for more information |
+
+**Basic Use**
+
+```javascript
+const item1 = createMetadataItem();
+const item2 = createMetadataItem();
+item1.setKey("mode");
+item2.SetKey("gender");
+cosnt metadataOption = {
+ majorRevision: 1234567;
+ enableRecordTs: true;
+ enableRecordUserId: true;
+};
+
+try{
+ await deleteLocalUserMetadata([item1,item2],metadataOption);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+> Caution:This operation will always excute regardless of the existing of metadata items
+
+#### Subscribe user metadata
+
+
+
+Subscribe to user metadata update events for a specific user.
+
+**Method**
+
+```javascript
+subscribeUserMetadata(uid: string): Promise;
+```
+
+| Parameter | Type | Required | Default | Description |
+|:---------:|:------:|:--------:|:-------:|:-----------------------:|
+| uid | string | yes | | Unique user identifier. |
+
+**Basic Use**
+
+```javascript
+try{
+ await subscribeUserMetadata("Tony");
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+#### Unsubscribe user metadata
+
+
+
+Unsubscribe to user metadata update events for a specific user.
+
+**Method**
+
+```javascript
+unsubscribeUserMetadata(uid: string): Promise;
+```
+
+| Parameter | Type | Required | Default | Description |
+|:---------:|:------:|:--------:|:-------:|:-----------------------:|
+| uid | string | yes | | Unique user identifier. |
+
+**Basic Use**
+
+```javascript
+try{
+ await unsubscribeUserMetadata("Tony");
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+#### User metadata event
+
+
+
+it will occur when user's metadata are updated(`Add`/`Set`/`Clear`/`Update`/`Delete`), You need to complete this procedure yourself, and then you can handle this event when you are subscribing user's metadata.
+
+**Method**
+
+```javascript
+UserMetaDataUpdated: (uid: string, data: RtmMetadata) => void;
+```
+
+**Basic Use**
+
+```javascript
+UserMetaDataUpdated: (uid: string, data: RtmMetadata) => {
+ // handle the event
+}
+```
+
+**Response**
+
+When this event occurs, you can recieve a uid which indicating whose metadata have changed and a type `RtmMetadata` for the user which contains the following properties:
+
+| Property Name | Type | Description |
+|:---------------:|:--------------------------:| -------------------------------------------------------------------------------- |
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single key-value metadata item for a user |
+| `majorRevision` | number | The major revision for this user metadata |
+
+The `RtmMetadataItem` is a basic unit item of a `userMetadata`, it contains the following properties:
+
+| Property Name | Type | Description |
+|:-------------------------------------:|:--------:| ------------------------------------------------------------------- |
+| `setKey(key: string): void` | function | Set key for the current `RtmMetadataItem` |
+| `getKey(): string` | function | Get key for the current `RtmMetadataItem` |
+| `setValue(value: string\|null): void` | function | Set Value for the current `RtmMetadataItem` |
+| `getValue(): string \| null` | function | Get Value for the current `RtmMetadataItem` |
+| `setRevision(revision: number): void` | function | Set revision for the current `RtmMetadataItem` |
+| `getRevision(): number` | function | Get revision for the current `RtmMetadataItem` |
+| `getUpdateTs(): number` | function | Get updated time for the current `RtmMetadataItem` |
+| `getAuthorUserId(): string` | function | Get the uid of who update this record for the current `RtmMetadataItem` |
+
+> **Caution**: It should be noted that the returned data contains the full amount of data of the current user, `Add`/`Set`/`Clear`/`Update`/`Delete` operation all will trigger this event, and you cannot distinguish which operation caused the current event. Need more features, you can use our new version 2.1.
+
+## Channel metadata
+
+#### Set channel metadata
+
+
+
+set the metadata of the channel.
+
+**Method**
+
+```javascript
+setChannelMetadata(
+ items: RtmMetadataItem[],
+ options?: RtmMetadataOptions
+ ): Promise;
+```
+
+| Property Name | Type | Description |
+|:-------------:|:--------------------------:|:----------------------------------------------------------------------------------------------------------------------------------:|
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single key-value metadata item for a channel,see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | Add optional properties to the current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions)for more information |
+
+The `RtmMetadataOptions` is a set of optional properties for operations, it contains the following properties:
+
+| Property Name | Type | Description |
+| -------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `majorRevision` | number | Revison control parametes. when the `majorRevision` you supplied is as same as the one in the storage, this operation will success. |
+| `enableRecordTs` | bool | When it is set to `true`, the final `RtmMetadataItem` will record the updating time automatically |
+| `enableRecordUserId` | bool | When it is set to `true`, the final `RtmMetadataItem` will record who have updated this item automatically |
+
+**Basic Use**
+
+```javascript
+const item1 = createMetadataItem();
+const item2 = createMetadataItem();
+item1.setKey("Announcement");
+item1.setValue("Welcome to RTM");
+item2.SetKey("Channel_type");
+item2.setValue("Public");
+cosnt metadataOption = {
+ majorRevision: 1234567;
+ enableRecordTs: true;
+ enableRecordUserId: true;
+};
+
+try{
+ await setChannelMetadata([item1,item2],metadataOption);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+> caution: This operation will `reset` all current metadata of channel and set a new one for a channel.
+
+#### Add channel metadata
+
+
+
+Add new metadata items to the channel.
+
+**Method**
+
+```javascript
+addChannelMetadata(
+ items: RtmMetadataItem[],
+ options?: RtmMetadataOptions
+ ): Promise;
+```
+
+| Property Name | Type | Description |
+|:-------------:|:--------------------------:|:----------------------------------------------------------------------------------------------------------------------------------:|
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single key-value metadata item for a channel,see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | Add optional properties to the current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions)for more information |
+
+**Basic Use**
+
+```javascript
+const item1 = createMetadataItem();
+const item2 = createMetadataItem();
+item1.setKey("Announcement");
+item1.setValue("Welcome to RTM");
+item2.SetKey("Channel_type");
+item2.setValue("Public");
+cosnt metadataOption = {
+ majorRevision: 1234567;
+ enableRecordTs: true;
+ enableRecordUserId: true;
+};
+
+try{
+ await addChannelMetadata([item1,item2],metadataOption);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+> Caution: This operation adds new metadata items for the channel. Reports errors if the key of new `RtmMetadataItem` already exists in the channel metadata.
+
+#### Clear channel metadata
+
+Delete all metadata items of the channel.
+
+**Method**
+
+```javascript
+clearChannelMetadata(options?: RtmMetadataOptions): Promise;
+```
+
+| Property Name | Type | Description |
+|:-------------:|:--------------------:|:------------------------------------------------------------------------------------------------------:|
+| `options` | `RtmMetadataOptions` | Add optional properties to the current channel metadata, see
+ [`RtmMetadataOptions`](#RtmMetadataOptions)for more information |
+
+**Basic Use**
+
+```javascript
+cosnt metadataOption = {
+ majorRevision: 1234567;
+};
+
+try{
+ await clearChannelMetadata(metadataOption);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+> Caution: This operation will clean all metadata of the specific channel.
+
+#### Update channel metadata
+
+
+
+Update metadata items of the channel.
+
+**Method**
+
+```javascript
+updateChannelMetadata(
+ items: RtmMetadataItem[],
+ options?: RtmMetadataOptions
+ ): Promise;
+```
+
+| Property Name | Type | Description |
+|:-------------:|:--------------------------:|:----------------------------------------------------------------------------------------------------------------------------------:|
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single key-value metadata item for a channel,see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | Add optional properties to the current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions)for more information |
+
+**Basic Use**
+
+```javascript
+const item1 = createMetadataItem();
+const item2 = createMetadataItem();
+item1.setKey("Announcement");
+item1.setValue("Welcome to RTM!");
+item2.SetKey("Channel_Type");
+item2.setValue("Public");
+cosnt metadataOption = {
+ majorRevision: 1234567;
+ enableRecordTs: true;
+ enableRecordUserId: true;
+};
+
+try{
+ await updateChannelMetadata([item1,item2],metadataOption);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+> Caution : This operation can only valid for updating the existing metadata items, or it will report errors.
+
+#### Delete channel metadata
+
+
+
+delete metadata items of the channel.
+
+**Method**
+
+```javascript
+deleteChannelMetadata(
+ items: RtmMetadataItem[],
+ options?: RtmMetadataOptions
+ ): Promise;
+```
+
+| Property Name | Type | Description |
+|:-------------:|:--------------------------:|:----------------------------------------------------------------------------------------------------------------------------------:|
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single key-value metadata item for a channel,see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | Add optional properties to the current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions)for more information |
+
+**Basic Use**
+
+```javascript
+const item1 = createMetadataItem();
+const item2 = createMetadataItem();
+item1.setKey("Announcement");
+item2.SetKey("Channel_Type");
+cosnt metadataOption = {
+ majorRevision: 1234567;
+ enableRecordTs: true;
+ enableRecordUserId: true;
+};
+
+try{
+ await deleteChannelMetadata([item1,item2],metadataOption);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+None
+
+> Caution:This operation will always excute regardless of the existing of metadata items
+
+#### Get channel metadata
+
+
+
+get all metadata items of the channel.
+
+**Method**
+
+```javascript
+getChannelMetadata(): Promise;
+```
+
+**Basic Use**
+
+```javascript
+try{
+ const channelMetadata = await getChannelMetadata("my_channel");
+ console.log(channelMetadata);
+}catch(status){
+ if (status){
+ const {code,message} = status;
+ console.log(code,message);
+ }
+}
+```
+
+**Response**
+
+Returns a type `RtmMetadata` for the specific `channel` which contains the following properties:
+
+| Property Name | Type | Description |
+| --------------- | -------------------------- | ----------------------------------------------------------------------------------- |
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single key-value metadata item for a channel |
+| `majorRevision` | number | The major revision for this channel metadata |
+
+The `RtmMetadataItem` is a basic unit item of a `ChannelMetadata`, it contains the following properties:
+
+| Property Name | Type | Description |
+| ------------------------------------- | -------- | ------------------------------------------------------------------- |
+| `setKey(key: string): void` | function | Set key for the current `RtmMetadataItem` |
+| `getKey(): string` | function | Get key for the current `RtmMetadataItem` |
+| `setValue(value: string\|null): void` | function | Set value for the current `RtmMetadataItem` |
+| `getValue(): string\|null` | function | Get the value for the current `RtmMetadataItem` |
+| `setRevision(revision: number): void` | function | Set revision for the current `RtmMetadataItem` |
+| `getRevision(): number` | function | Get revision for the current `RtmMetadataItem` |
+| `getUpdateTs(): number` | function | Get updated time for the current `RtmMetadataItem` |
+| `getAuthorUserId(): string` | function | Get the uid of who update this record for the current `RtmMetadataItem` |
+
+> **Caution**: If the specific `channel` have not set any user metadata yet, this operation will return a empty {}.
+
+#### Listen channel metadata update event
+
+When you join channel ,you will automatically Listen the channel metadata update event
+
+#### Channel metadata event
+
+
+
+it will occur when channel's metadata are updated, You need to complete this procedure yourself, and then you can handle this event when you are in the channel
+
+**Method**
+
+```javascript
+ChannelMetaDataUpdated: (data: RtmMetadata) => void;
+```
+
+**Basic Use**
+
+```javascript
+channel.on('ChannelMetaDataUpdated', (data) => {
+ console.log(data); //
+});
+```
+
+**Response**
+
+When this event occurs, you can recieve a type `RtmMetadata` for the specific `channel` which contains the following properties:
+
+| Property Name | Type | Description |
+| --------------- | -------------------------- | ----------------------------------------------------------------------------------- |
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single key-value metadata item for a channel |
+| `majorRevision` | number | The major revision for this channel metadata |
+
+The `RtmMetadataItem` is a basic unit item of a `ChannelMetadata`, it contains the following properties:
+
+| Property Name | Type | Description |
+| ------------------------------------- | -------- | ------------------------------------------------------------------- |
+| `setKey(key: string): void` | function | Set key for the current `RtmMetadataItem` |
+| `getKey(): string` | function | Get key for the current `RtmMetadataItem` |
+| `setValue(value: string\|null): void` | function | Set value for the current `RtmMetadataItem` |
+| `getValue(): string\|null` | function | Get the value for the current `RtmMetadataItem` |
+| `setRevision(revision: number): void` | function | Set revision for the current `RtmMetadataItem` |
+| `getRevision(): number` | function | Get revision for the current `RtmMetadataItem` |
+| `getUpdateTs(): number` | function | Get updated time for the current `RtmMetadataItem` |
+| `getAuthorUserId(): string` | function | Get the uid of who update this record for the current `RtmMetadataItem` |
+
+> **Caution**: It should be noted that the returned data contains the full amount of data of the current channel, `Add`/`Set`/`Clear`/`Update`/`Delete` operation all will trigger this event, and you cannot distinguish which operation caused the current event. Need more features, you can use our new version 2.1.
+
+
diff --git a/shared/signaling/metadata-sdk/windows.mdx b/shared/signaling/metadata-sdk/windows.mdx
new file mode 100644
index 000000000..2cc5e94ae
--- /dev/null
+++ b/shared/signaling/metadata-sdk/windows.mdx
@@ -0,0 +1,4 @@
+
+
+ Metadata SDK is not currently available for this platform.
+
diff --git a/shared/signaling/prerequisites.mdx b/shared/signaling/prerequisites.mdx
new file mode 100644
index 000000000..02e6c4231
--- /dev/null
+++ b/shared/signaling/prerequisites.mdx
@@ -0,0 +1,17 @@
+import PlatformPrerequisites from '@docs/shared/common/prerequities.mdx';
+
+
+
+- Enabled in [ Console](https://console.agora.io/v2). To do so:
+ 1. Go to **Subscriptions** > **Signaling**
+ 1. Subscribe to a plan.
+
+ ![signaling-pricing-page](/images/signaling/signaling-pricing-plans.png)
+
+ Once subscribed, you will be able to unsubscribe from the same page.
+
+- [Raised a support ticket](https://agora-ticket.agora.io/) to activate the Stream Channel, if necessary.
+
+ The support team confirms activation through a ticket update.
+
+Signaling 2.x is an enhanced version compared to 1.x with a wide range of new features. It follows a new pricing structure. Please visit the [Pricing](../overview/pricing) page for details.
diff --git a/shared/signaling/presence/_presence.mdx b/shared/signaling/presence/_presence.mdx
new file mode 100644
index 000000000..36ed2d68d
--- /dev/null
+++ b/shared/signaling/presence/_presence.mdx
@@ -0,0 +1,53 @@
+import * as data from '@site/data/variables';
+import ProjectImplement from '@docs/shared/signaling/presence/project-implementation/index.mdx';
+import ProjectTest from '@docs/shared/signaling/presence/project-test/index.mdx';
+import Reference from '@docs/shared/signaling/presence/reference/index.mdx';
+
+In real-time messaging solutions, it is often important to know whether a user is currently online or offline. For example, in instant messaging, chat applications, and online collaboration tools, users need to see the availability of their contacts. This information is typically displayed as a status message or icon next to a user's name. Presence features in enable you to monitor join, leave, and status change notifications for users in a channel. Using Presence, you can:
+
+- Get a list of users currently in a channel and their temporary status data.
+- Get a list of channels a specified user has joined or is subscribed to.
+- Read, set, or remove user status.
+- Receive real-time event notifications when users join or leave specified channels.
+- Receive user status change event notifications in real time.
+
+
+**The implementation is coming soon.**
+
+
+
+
+## Understand the tech
+
+Presence provides real-time information about the availability, and the current status of users, for effective communication and collaboration. It enables you to retrieve a list of users in a channel, or to query the list of channels for a specific user. The following figure illustrates how you integrate presence features into your .
+
+![](/images/signaling/presence-workflow.svg)
+
+
+## Prerequisites
+
+To follow this page, you must have:
+
+- Set up the [ reference app](/en/signaling/get-started/get-started-sdk#project-setup).
+
+
+## Implement presence
+
+To implement presence,
+
+
+
+## Test presence
+
+This section explains how to run the reference app and test presence features. To run the project, take the following steps:
+
+
+
+## Reference
+
+This section contains additional information that either supplements the content on this page or directs you to documentation that covers other aspects of this product.
+
+
+
+
+
diff --git a/shared/signaling/presence/project-implementation/index.mdx b/shared/signaling/presence/project-implementation/index.mdx
new file mode 100644
index 000000000..a614f0b56
--- /dev/null
+++ b/shared/signaling/presence/project-implementation/index.mdx
@@ -0,0 +1,3 @@
+import Web from './web.mdx'
+
+
diff --git a/shared/signaling/presence/project-implementation/web.mdx b/shared/signaling/presence/project-implementation/web.mdx
new file mode 100644
index 000000000..0843f4dcb
--- /dev/null
+++ b/shared/signaling/presence/project-implementation/web.mdx
@@ -0,0 +1,90 @@
+
+
+### Add the presence event listener
+
+ ```js
+ signalingEngine.addEventListener("presence", eventArgs => {
+ eventsCallback("presence", eventArgs);
+ if (eventArgs.eventType === "SNAPSHOT") {
+ messageCallback(
+ `User ${eventArgs.snapshot[0].userId} joined channel ${eventArgs.channelName}`
+ );
+ } else {
+ messageCallback(
+ "Presence event: " +
+ eventArgs.eventType +
+ ", User: " +
+ eventArgs.publisher
+ );
+ }
+ });
+ ```
+
+### Enable presence notifications when you subscribe to a channel
+
+ ```js
+ const subscribe = async (channelName) => {
+ channelName = channelName || config.channelName;
+ try {
+ const subscribeOptions = {
+ withMessage: true,
+ withPresence: true, // Enable presence notifications
+ withMetadata: true,
+ withLock: true,
+ };
+ await signalingEngine.subscribe(channelName, subscribeOptions);
+ } catch (error) {
+ console.log(error);
+ }
+ };
+ ```
+
+### Obtain a list of users in the channel
+
+ To get a list of users in a channel, call `getOnlineUsers()`:
+
+ ```js
+ const getOnlineMembersInChannel = async (channelName, channelType) => {
+ const result = await getSignalingEngine().presence.getOnlineUsers(
+ channelName,
+ channelType
+ );
+ return result.occupants;
+ };
+ ```
+
+### Set local user status
+
+ To set the local user status, call `setState`.
+
+ ```js
+ var state = {"mood":"pumped", "isTyping": "false"};
+
+ const setUserState = async (channelName, channelType, state) => {
+ try {
+ const result = await getSignalingEngine().presence.setState(
+ channelName, channelType, state);
+ console.log(result);
+ } catch (error) {
+ console.log(error);
+ }
+ };
+ ```
+
+### Get the status of a remote user
+
+ To read the status of a remote user, call `getState`.
+
+ ```js
+ const getUserState = async (userId, channelName, channelType) => {
+ try {
+ const result = await getSignalingEngine().presence.getState(userId,
+ channelName, channelType);
+ console.log(result);
+ } catch (error) {
+ console.log(error);
+ }
+ };
+ ```
+
+
\ No newline at end of file
diff --git a/shared/signaling/presence/project-test/index.mdx b/shared/signaling/presence/project-test/index.mdx
new file mode 100644
index 000000000..4bad50030
--- /dev/null
+++ b/shared/signaling/presence/project-test/index.mdx
@@ -0,0 +1,12 @@
+import Web from './web.mdx'
+
+In , each authentication token you create is specific for a user ID. You create a token for each
+user in the channel, each user must log in from different instance of your .
+To test , you run 2 or more instances of your . When you call `login` using , ensure that the UID is the same as you used to create the token.
+
+For each user in your tests:
+
+1. **[Generate a temporary token](../reference/manage-agora-account#generate-a-temporary-token)**.
+
+
diff --git a/shared/signaling/presence/project-test/web.mdx b/shared/signaling/presence/project-test/web.mdx
new file mode 100644
index 000000000..83f03d9f1
--- /dev/null
+++ b/shared/signaling/presence/project-test/web.mdx
@@ -0,0 +1,34 @@
+
+2. **Configure the project**:
+
+ In `/src/signaling_manager/config.json`:
+
+ 1. Replace the value for `token` with the token.
+
+ 1. Replace the value for `appId` with the value from .
+
+ 1. Replace the value for UID with the the same value you used to generate the token.
+
+ 1. Ensure that the `channelName` is filled in. The channel name can be any string.
+
+1. **Run the reference app**:
+
+ Open Terminal in the project folder, then run the following command:
+
+ ``` bash
+ pnpm dev
+ ```
+
+ Use the URL displayed in Terminal to open the app in your browser.
+
+1. **Test presence functionality**:
+
+ 1. Choose **SDK quickstart**, then log in to .
+ 1. Open the **Token authentication** example in multiple tabs.
+ 1. Log in to from each tab using a different user ID.
+ 1. Subscribe to the same channel from all the tabs.
+ You see a list of all users in the channel in the **SDK quickstart** tab.
+ 1. Unsubscribe from the channel in another tab.
+ You see that the corresponding user ID disappears from the user list.
+
+
diff --git a/shared/signaling/presence/reference/index.mdx b/shared/signaling/presence/reference/index.mdx
new file mode 100644
index 000000000..a614f0b56
--- /dev/null
+++ b/shared/signaling/presence/reference/index.mdx
@@ -0,0 +1,3 @@
+import Web from './web.mdx'
+
+
diff --git a/shared/signaling/presence/reference/web.mdx b/shared/signaling/presence/reference/web.mdx
new file mode 100644
index 000000000..49f5ae810
--- /dev/null
+++ b/shared/signaling/presence/reference/web.mdx
@@ -0,0 +1,10 @@
+
+
+### API reference
+
+* [Presence](../reference/api#presence)
+* [Event listeners](../reference/api#event-listeners)
+* [Presence Event Types](../reference/api#presence-event-types)
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/reference/_pricing.mdx b/shared/signaling/reference/_pricing.mdx
new file mode 100644
index 000000000..d73132dab
--- /dev/null
+++ b/shared/signaling/reference/_pricing.mdx
@@ -0,0 +1,68 @@
+
+
+## Pricing concepts
+
+ is priced based on the total number of messages, peak connections, and storage usage of your account in a calendar month.
+
+### Number of messages
+
+The measurement rules for the number of messages sent and received are as follows:
+
+* Any 1 message published in the Message Channel counts as 1 message
+* Any 1 message received in the Message Channel counts as 1 message.
+
+For example, if a client sends 1 message to a Message Channel, and the channel is subscribed by 10 people, it is counted as 1 sent message and 10 received messages, for a total of 11 messages.
+
+* Any 1 message published in a Topic in the Stream Channel counts as 1 message.
+* Any 1 message received in a Topic in the Stream Channel counts as 1 message.
+
+For example, if a client sends a message to a Topic in the Stream Channel, and this Topic is subscribed by 10 people, it is counted as 1 sent message and 10 received messages, for a total of 11 messages.
+
+Even if message filtering is enabled on the client, it does not have any impact on message metering. The client-side message filtering function is only for the convenience of developers, and the messages are actually delivered.
+The asynchronous callback generated by publishing a message in the Message Channel or Stream Channel is not counted in the number of messages.
+Messages sent using the RESTful API are also counted as messages.
+If a client does not subscribe to a channel or topic, it does not receive any messages.
+
+### Presence
+Each Presence event notification published is counted as one message, such as the client’s entry into the channel, leaving the channel, timeout notification, and status change notification.
+Each Presence event notification received in the channel counts as one message. For example, if a client enters a channel, and 10 other clients subscribe to the channel and listen to the Presence event notifications in the channel, then this counts as 1 sent Presence event notification, and the remaining 10 clients receive a total of 10 presence event notifications. That is a total of 11 messages.
+
+The client message filtering function has no impact on the presence event notification count.
+If a client does not want to send the Presence event when joining the Stream Channel or subscribing to the Message Channel, setting `withPresence` to `false` does not affect the calculation of the number of messages.
+If a client does not join a Stream Channel or subscribe to a Message Channel, it does not receive the Presence event notification of the channel.
+
+### Storage
+Setting, querying, updating, and deleting any piece of Channel Metadata in the Message Channel or Stream Channel is counted as 1 message.
+Any 1 Channel Metadata change event notification received in a Message Channel or Stream Channel is counted as 1 message. For example, if a client sets 1 Channel Metadata on a channel, the channel is subscribed by 10 people, and these users listen to the Channel Metadata change notification, it is deemed that 1 message has been published and 10 messages have been received, for a total of 11 messages.
+
+Setting, querying, updating, and deleting any piece of User Metadata is counted as 1 message.
+Receiving 1 change notification of User Metadata is counted as 1 message. For example, if the client sets 1 User Metadata for a user, and the User Metadata is subscribed by 10 people, it is deemed that 1 message has been published and 10 messages have been received, for a total of 11 messages.
+
+Client message filtering has no impact on Channel Metadata or User Metadata change notification counts. The client message filtering settings are as follows:
+
+* Set `withMetadata` to `false` if the client does not want to receive Channel Metadata or User Metadata change notifications when joining a Stream Channel or subscribing to a Message Channel.
+* If a client does not join the Stream Channel or subscribe to a Message Channel, it does not receive the Channel Metadata change notification of the channel.
+* If the client does not subscribe to other users’ User Metadata, it will not receive other users’ User Metadata change notifications.
+
+### Locks
+Any operations such as lock setting, query, release, deprivation, and deletion are counted as 1 message.
+Any 1 lock change event notification received in a Message Channel or Stream Channel is counted as one message. For example, if the client sets a lock on a channel, and the channel is subscribed by 10 people, and these users listen to the lock change notification, it is regarded as publishing 1 message and receiving 10 messages, for a total of 11 messages.
+
+In , a message is calculated in 1 KB. Therefore, if you send a message package with a size of 10 KB to a channel or Topic subscribed by 100 people, it will be counted as 10 inbound messages and 1,000 outbound messages to give a total of 1,010 messages.
+
+### Storage occupancy
+
+Storage in generates cloud storage occupancy. measures the total storage occupancy, and bills the amount for the month by sampling the customer's actual cloud storage occupancy at 1-hour intervals in a natural month and then accumulating them. The calculation formula is as follows:
+
+_Storage fees = number of days in the month * 24 hours * storage usage per hour * sampling rate_
+
+For example, if you have 100 GB of storage and you are using a sampling rate of 10%, then your storage fees for a 30-day month would be:
+
+_Storage fees = 30 days * 24 hours * 100 GB/hour * 10% = 72,000 GB_
+
+This would be equivalent to 72 TB, which is a common unit of measurement for storage capacity.
+It is important to note that this formula is just a general guideline. The actual storage fees you are charged may vary depending on your provider and your specific contract.
+
+### Peak connections
+
+The number of peak connections is the maximum number of real-time clients simultaneously connected to at any point in a calendar month. For example, if you have 10,000 customers, and a maximum of 500 clients connect to simultaneously in a given month, the PCU value is 500. This means you only pay for 500 peak connections. The total number of clients or devices connected to in a month does not affect billing.
\ No newline at end of file
diff --git a/shared/signaling/reference/api-ref/_channel-en.javascript.mdx b/shared/signaling/reference/api-ref/_channel-en.javascript.mdx
new file mode 100644
index 000000000..b6345b43f
--- /dev/null
+++ b/shared/signaling/reference/api-ref/_channel-en.javascript.mdx
@@ -0,0 +1,275 @@
+import * as config from './shared/_configuration.mdx'
+import * as topic from './shared/_topic.mdx'
+import * as channel from './shared/_channel.mdx'
+import * as enumv from './shared/_enumv.mdx'
+import Status from './shared/_rtmstatus.mdx'
+
+Signaling provides a highly efficient channel management mechanism for data transmission. Any user who subscribes or joins a channel can receive messages and events transmitted within 100 milliseconds. Signaling allows clients to subscribe to hundreds or even thousands of channels. Most Signaling APIs perform actions such as sending, receiving, and encrypting based on channels.
+
+Based on capabilities of Agora, Signaling channels are divided into two types to match different application scenarios:
+
+- Message Channel: Follows the industry-standard Pub/Sub (publish/subscribe) mode. You can send and receive messages within the channel by subscribing to a channel, and do not need to create the channel in advance. There is no limit to the number of publishers and subscribers in a channel.
+
+- Stream Channel: Follows a concept similar to the observer pattern in the industry, where users need to create and join a channel before sending and receiving messages. You can create different topics in the channel, and messages are organized and managed through topics.
+
+### {channel.subscribe[props.ag_platform]}
+
+#### Description
+
+Signaling provides event notification capabilities for messages and states. By setting up event listeners, you can receive messages and events within subscribed channels. For information on how to add and set up event listeners, see Event Listener.
+
+By calling the {channel.subscribe[props.ag_platform]} method, the client can subscribe to a message channel and start receiving messages and event notifications within the channel. After successfully calling this method, users who subscribe to the channel and enable the presence event listener can receive a {config.onpresenceevent[props.ag_platform]} event with the {enumv.remotejoinchannel[props.ag_platform]} type.
+
+This method only applies to the message channel.
+
+#### Method
+
+You can call the {channel.subscribe[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.subscribe(
+ channelName: string,
+ options?: object
+): Promise;
+```
+
+| Parameter | Type | Required | Default | Description |
+| :-----------: | :----: | :------: | :-----: | :----------------------: |
+| `channelName` | string | Yes | - | The channel name. |
+| `options` | object | Optional | - | Options for subscribing a channel. |
+
+The options object includes the following properties:
+
+| Property | Type | Required | Default | Description |
+| :------------: | :-----: | :------: | :-----: | :----------------------------------------------------------: |
+| `withMessage` | boolean | Optional | `true` | Whether to subscribe to message event notifications in the channel. |
+| `withPresence` | boolean | Optional | `true` | Whether to subscribe to presence event notifications in the channel. |
+| `withMetadata` | boolean | Optional | `false` | Whether to subscribe to storage event notifications in the channel. |
+| `withLock` | boolean | Optional | `false` | Whether to subscribe to lock event notifications in the channel. |
+
+#### Basic usage
+
+```javascript
+const options ={
+ withMessage : true,
+ withPresence : true,
+ withMetadata : false,
+ withLock : false
+};
+try {
+ const result = await rtm.subscribe("chat_room", options);
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+#### Return value
+
+If the method call succeeds, the {channel.subscriberesulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type SubscribeResponse = {
+ timeToken : number // Reserved property, indicating the timestamp of the successful operation.
+ channelName : string // Channel name.
+}
+```
+
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
+
+### {channel.unsubscribe[props.ag_platform]}
+
+#### Description
+
+To cancel your subscription, call the {channel.unsubscribe[props.ag_platform]} method. After successfully unsubscribing from the channel, other users who subscribe to the channel and enable event listeners can receive a {config.onpresenceevent[props.ag_platform]} event notification with the {enumv.remoteleavechannel[props.ag_platform]} type. For details, see Event Listener.
+
+This method only applies to the message channel.
+
+#### Method
+
+You can call the {channel.unsubscribe[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.unsubscribe(
+ channelName: string
+): Promise;
+```
+
+| Parameter | Type | Required | Default | Description |
+| :-----------: | :----: | :------: | :-----: | :----------------------: |
+| `channelName` | string | Yes | - | The channel name. |
+
+#### Basic usage
+
+```javascript
+try {
+ const result = await rtm.unsubscribe("chat_room");
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+#### Return value
+
+If the method call succeeds, the {channel.unsubscriberesulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type UnsubscribeResponse = {
+ timeToken : number // Reserved property, indicating the timestamp of the successful operation.
+ channelName : string // Channel name.
+}
+```
+
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
+
+### {channel.create[props.ag_platform]}
+
+#### Description
+
+Before using a stream channel, you need to call the {channel.create[props.ag_platform]} method to create an {channel.istreamchannel[props.ag_platform]} instance. After successfully creating the instance, you can call its relevant methods to implement functions, such as joining the channel, leaving the channel, sending messages in a topic, and subscribing to messages in a topic.
+
+This method only applies to the stream channel.
+
+#### Method
+
+You can call the {channel.create[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.createStreamChannel(chanelName: string): RTMStreamChannel;
+```
+
+| Parameter | Type | Required | Default | Description |
+| :-----------: | :----: | :------: | :-----: | :----------------------: |
+| `channelName` | string | Yes | - | The channel name. |
+
+
+#### Basic usage
+
+```javascript
+try{
+ const Loc_stChannel = await rtm.createStreamChannel( "Location");
+ console.log("Create Stream Channel success!: ");
+} catch (status){
+ console.log(status);
+}
+```
+
+#### Return value
+
+An {channel.istreamchannel[props.ag_platform]} instance.
+
+
+
+### {channel.join[props.ag_platform]}
+
+#### Description
+
+After successfully creating a stream channel, you can call the {channel.join[props.ag_platform]} method to join the stream channel. Once you join the channel, you can implement channel-related functions. At this point, users who subscribe to the channel and add event listeners can receive the following event notifications:
+
+- Local users:
+ - {config.onpresenceevent[props.ag_platform]} event notification with the {enumv.presencetypesnap[props.ag_platform]} type.
+ - {config.ontopicevent[props.ag_platform]}event notification with the {enumv.topictypesnap[props.ag_platform]} type.
+- Remote users: {config.onpresenceevent[props.ag_platform]} event notification with the {enumv.remotejoinchannel[props.ag_platform]} type.
+
+This method only applies to the stream channel.
+
+#### Method
+
+You can call the {channel.join[props.ag_platform]} method in the following way:
+
+```javascript
+join(options?: {
+ token?: string;
+ withPresence?: boolean;
+ withMetadata?: boolean;
+ withLock?: boolean;
+}): Promise;
+```
+
+| Parameter | Type | Required | Default | Description |
+| :-----------: | :----: | :------: | :-----: | :----------------------: |
+| `options` | object | Optional | - | Options for joining a channel. |
+
+The options object includes the following properties:
+
+| Property | Type | Required | Default | Description |
+| :------------: | :-----: | :------: | :-----: | :----------------------------------------------------------: |
+| `token` | string | Optional | - | The token used for joining a stream channel, which is currently the same as the RTC token. |
+| `withMetadata` | boolean | Optional | `false` | Whether to subscribe to storage event notifications in the channel. |
+| `withPresence` | boolean | Optional | `true` | Whether to subscribe to presence event notifications in the channel. |
+| `withLock` | boolean | Optional | `false` | Whether to subscribe to lock event notifications in the channel. |
+
+#### Basic usage
+
+```js
+const options ={
+ token : "yourToken",
+ withPresence : true,
+ withMetadata : false,
+ withLock : false
+};
+try {
+ const result = await stChannel.join(options);
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+
+#### Return value
+
+If the method call succeeds, the {channel.joinresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type JoinChannelResponse = {
+ timeToken : number , // Reserved property, indicating the timestamp of the successful operation.
+ channelName : string // Channel name.
+}
+```
+
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
+
+
+
+### {channel.leave[props.ag_platform]}
+
+#### Description
+
+Call the {channel.leave[props.ag_platform]} method to leave the channel. After leaving the channel, you can no longer receive any messages, states, or event notifications from this channel. At the same time, you can no longer be the topic publisher or subscriber of all topics. To restore your previous publisher role and subscribing relationship, call {channel.join[props.ag_platform]}, {topic.join[props.ag_platform]} and {topic.subscribe[props.ag_platform]} methods in order.
+
+After successfully leaving the channel, remote users in the channel can receive a {config.onpresenceevent[props.ag_platform]} event notification with the {enumv.remoteleavechannel[props.ag_platform]} type. For details, see Event Listener.
+
+This method only applies to the stream channel.
+
+#### Method
+
+You can call the {channel.leave[props.ag_platform]} method in the following way:
+
+```javascript
+leave(): Promise;
+```
+
+#### Basic usage
+
+```javascript
+try{
+ const result = await rtm.leave();
+ console.log(result);
+} catch (status){
+ console.log(status);
+}
+```
+
+#### Return value
+
+If the method call succeeds, the {channel.leaveresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type LeaveChannelResponse = {
+ timeToken : number // Reserved property, indicating the timestamp of the successful operation.
+ channelName : string // Channel name.
+}
+```
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
\ No newline at end of file
diff --git a/shared/signaling/reference/api-ref/_configuration-en.javascript.mdx b/shared/signaling/reference/api-ref/_configuration-en.javascript.mdx
new file mode 100644
index 000000000..bf0532569
--- /dev/null
+++ b/shared/signaling/reference/api-ref/_configuration-en.javascript.mdx
@@ -0,0 +1,271 @@
+import * as config from './shared/_configuration.mdx'
+import * as enumv from './shared/_enumv.mdx'
+import Status from './shared/_rtmstatus.mdx'
+
+Before initializing a Signaling client instance, you need to import the Signaling Javascript SDK into your project:
+
+```js
+
+```
+
+### Initialization
+
+#### Description
+
+Initialization in Signaling refers to creating and initializing a Signaling client instance. When initializing the instance, you need to pass in parameters including `appId` and `userId`. You can create a project and get the App ID on the Agora console.
+
+
The initialization step needs to be completed before calling the other Signaling APIs.
In order to identify users and devices, you need to ensure that userId is globally unique and remains constant throughout the lifecycle of the user or device.
+
+#### Method
+
+You can create and initialize an instance in the following way:
+
+```javascript
+class RTM(
+ constructor(
+ appId: string,
+ userId: string,
+ rtmConfig?: {
+ token: string,
+ encryptionMode: string,
+ cipherKey: string,
+ salt: Uint8Array,
+ useStringUserId: boolean,
+ presenceTimeout: number,
+ logUpload: boolean,
+ logLevel: string,
+ cloudProxy: boolean
+ }
+ );
+)
+```
+
+| Parameter | Type | Required | Default | Description |
+| :---------: | :------------------------------------------------: | :------: | :----: | ------------------------------------------------------------------------------- |
+| `appId` | string | Required | - | The App ID of your Agora project on the Agora Console. |
+| `userId` | string | Required | - | The unique ID to identify a user or device. |
+| `rtmConfig` | {config.rtm[props.ag_platform]} | Optional | - | The configuration parameters for initialization, see {config.rtm[props.ag_platform]}. |
+
+#### Basic usage
+
+```javascript
+const { RTM } = AgoraRTM;
+const rtm = new RTM(
+ appId : "myAppId",
+ userId : "Tony"
+);
+```
+
+
+#### Return Value
+
+A Signaling client instance. Now you can call other Signaling APIs.
+
+### {config.rtm[props.ag_platform]}
+
+#### Description
+
+{config.rtm[props.ag_platform]} is used to configure additional properties when you initialize a Signaling client instance. These configuration properties take effect throughout the lifecycle of the Signaling client and affect the behaviors of the Signaling client.
+
+#### Method
+
+You can create a {config.rtm[props.ag_platform]} instance in the following way:
+
+```js
+const { RTMConfig } = AgoraRTM;
+```
+
+| Property | Type | Required | Default | 描述 |
+| :---------------: | :--------: | :------: | :-----: | ---------------------------------------- |
+| `token` | string | Optional | - | A dynamic key, usually generated by a token server. |
+| `encryptionMode` | string | Optional | - | Encryption mode for end-to-end messages. To disable end-to-end encryption, set this property to {enumv.encryptionmodenone[props.ag_platform]}. For details, see [Encryption Modes](#end-to-end-encryption-modes). |
+| `cipherKey` | string | Optional | - | The key used for encryption and decryption. Set this property to enable message encryption. |
+| `salt` | Uint8Array | Optional | - | The salt required for encryption. The value must be a 32-byte binary array. |
+| `useStringUserId` | boolean | Optional | `true` | Whether to use string-type user IDs:
true: Use string-type user IDs.
false: Use number-type user IDs. When set the property as false, SDK automatically converts string-type user IDs to number-type ones. In this case, the `userId` parameter must be a numeric string (for example, "123456"), otherwise initialization fails.
|
+| `presenceTimeout` | number | optional | `300` | Presence timeout in seconds, and the value range is [10,300]. |
+| `logUpload` | boolean | Optional | `false` | Whether to upload logs to the server:
true: Enable log upload
false: Disable log upload.
|
+| `logLevel` | string | Optional | - | Set the output level of SDK log. For details, see log output level. |
+| `cloudProxy` | boolean | Optional | `false` | Whether to enable the cloud proxy:
true: Enable.
false: Disable.
|
+
+#### Basic usage
+
+```js
+const { RTM, EncryptionMode } = AgoraRTM;
+const rtmConfig = {
+ token : "yourToken",
+ encryptionMode : EncryptionMode.AES_256_GCM,
+ slat : yourSalt,
+ cipherKey : "yourCipherKey",
+ presenceTimeout : 300,
+ logUpload : true,
+ logLevel : 'debug',
+ cloudProxy : false,
+ useStringUserId : false
+};
+const rtm = new RTM( "myAppId", "Tony", rtmConfig);
+```
+
+### Event Listeners
+
+#### Description
+
+In Signaling there are seven types of events as follows:
+
+| Event Type | Description |
+|:----------------------------:| ------------------------- |
+| {config.onmessageevent[props.ag_platform]} | Receive message event notifications in subscribed message channels and subscribed topics. |
+| {config.onpresenceevent[props.ag_platform]} | Receive presence event notifications in subscribed message channels and joined stream channels. |
+| {config.ontopicevent[props.ag_platform]} | Receive all topic event notifications in joined stream channels. |
+| {config.onstorageevent[props.ag_platform]} | Receive channel metadata event notifications in subscribed message channels and joined stream channels, and the user metadata event notification of the subscribed users. |
+| {config.onlockevent[props.ag_platform]} | Receive lock event notifications in subscribed message channels and joined stream channels. |
+| {config.onconnection[props.ag_platform]} | Receive event notifications when client connection status changes. |
+| {config.ontokenwillexpire[props.ag_platform]} | Receive event notifications when the client tokens are about to expire. |
+
+#### Add event listeners
+
+You can add event listeners in the following way:
+
+```javascript
+// Add message event listeners
+// Message
+rtm.addEventListener("message", event => {
+ const channelType = event.channelType; // Which channel type it is, Should be "STREAM" or "MESSAGE" .
+ const channelName = event.channelName; // Which channel does this message come from
+ const topic = event.topicName; // Which Topic does this message come from, it is valid when the channelType is "STREAM".
+ const messageType = event.messageType; // Which message type it is, Should be "sting" or "binary" .
+ const customType = event.customType; // User defined type
+ const publisher = event.publisher; // Message publisher
+ const message = event.message; // Message payload
+ const pubTime = event.publishTime; // Message publisher timestamp
+});
+// Presence
+rtm.addEventListener("presence", event => {
+ const action = event.eventType; // Which action it is ,should be one of 'SNAPSHOT'、'INTERVAL'、'JOIN'、'LEAVE'、'TIMEOUT、'STATE_CHANGED'、'OUT_OF_SERVICE'.
+ const channelType = event.channelType; // Which channel type it is, Should be "STREAM" or "MESSAGE" .
+ const channelName = event.channelName; // Which channel does this event come from
+ const publisher = event.publisher; // Who trigger this event
+ const states = event.stateChanged; // User state payload
+ const interval = event.interval; // Interval payload
+ const snapshot = event.snapshot; // Snapshot payload
+});
+// Topic
+rtm.addEventListener("topic", event => {
+ const action = event.evenType; // Which action it is ,should be one of 'SNAPSHOT'、'JOIN'、'LEAVE'.
+ const channelName = event.channelName; // Which channel does this event come from
+ const publisher = event.userId; // Who trigger this event
+ const topicInfos = event.topicInfos; // Topic information payload
+ const totalTopics = event.totalTopics; // How many topics
+});
+// Storage
+rtm.addEventListener("storage", event => {
+ const channelType = event.channelType; // Which channel type it is, Should be "STREAM" or "MESSAGE" .
+ const channelName = event.channelName; // Which channel does this event come from
+ const publisher = event.publisher; // Who trigger this event
+ const storageType = event.storageType; // Which category the event is, should be 'USER'、'CHANNEL'
+ const action = event.eventType; // Which action it is ,should be one of "SNAPSHOT"、"SET"、"REMOVE"、"UPDATE" or "NONE"
+ const data = event.data; // 'USER_METADATA' or 'CHANNEL_METADATA' payload
+});
+// Lock
+rtm.addEventListener("lock", event => {
+ const channelType = event.channelType; // Which channel type it is, Should be "STREAM" or "MESSAGE" .
+ const channelName = event.channelName; // Which channel does this event come from
+ const publisher = event.publisher; // Who trigger this event
+ const action = event.evenType; // Which action it is ,should be one of 'SET'、'REMOVED'、'ACQUIRED'、'RELEASED'、'EXPIRED'、'SNAPSHOT'
+ const lockName = event.lockName; // Which lock it effect
+ const ttl = event.ttl; // The ttl of this lock
+ const snapshot = event.snapshot; // Snapshot payload
+});
+// Connection State Change
+rtm.addEventListener("status", event => {
+ const currentState = event.state; // Which connection state right now
+ const changeReason = event.reason; // Why trigger this event
+});
+// Token Privilege Will Expire
+rtm.addEventListener("tokenPrivilegeWillExpire", (channelName) => {
+ const channelName = channelName; // Which Channel Token Will Expire
+});
+```
+
+#### Remove event listeners
+
+You can call the {config. removedelegate[props.ag_platform]} method to remove a specified event listener.
+
+```javascript
+rtm.removeEventListener('status', statusHandler);
+```
+
+### {config.login[props.ag_platform]}
+
+#### Description
+
+After creating and initializing a Signaling client instance, you need to perform the {config.login[props.ag_platform]} operation to log in to the Signaling service. With successful login, the client establishes a long link to the Signaling server and allows the client to access Signaling resources.
+
+#### Method
+
+You can call the {config.login[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.login(): Promise;
+```
+
+#### Basic usage
+
+```javascript
+try{
+ const result = await rtm.login();
+ console.log(result);
+} catch (status){
+ console.log(status);
+}
+```
+
+
+#### Return Value
+
+If the method call succeeds, the {config.loginresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type LoginResponse = {
+ timeToken: number // Reserved property, indicating the timestamp of the successful operation
+}
+```
+
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
+
+
+### {config.logout[props.ag_platform]}
+
+#### Description
+
+Log out of the Signaling.
+
+#### Method
+
+You can call the {config.logout[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.logout(): Promise;
+```
+
+#### Basic usage
+
+```js
+try{
+ const result = await rtm.logout();
+ console.log(result);
+} catch (status){
+ console.log(status);
+}
+```
+
+#### Return Value
+
+If the method call succeeds, the {config.logoutresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type LoginResponse = {
+ timeToken: number // Reserved property, indicating the timestamp of the successful operation
+}
+```
+
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
\ No newline at end of file
diff --git a/shared/signaling/reference/api-ref/_enumv-en.mdx b/shared/signaling/reference/api-ref/_enumv-en.mdx
new file mode 100644
index 000000000..9e7c6c624
--- /dev/null
+++ b/shared/signaling/reference/api-ref/_enumv-en.mdx
@@ -0,0 +1,101 @@
+import * as config from './shared/_configuration.mdx'
+import * as enumv from './shared/_enumv.mdx'
+
+### Channel Types
+
+| Value | Description |
+| :----------: | ---------------------------------------------------------- |
+| {enumv.channeltypemessage[props.ag_platform]} | Message channel. |
+| {enumv.channeltypestream[props.ag_platform]} | Stream channel. |
+
+
+### Reasons for the SDK Connection State Change
+
+| Value | Description |
+| :----------: | ---------------------------------------------------------- |
+| {enumv.reasonconnecting[props.ag_platform]} | The SDK is connecting with the Signaling system. |
+| {enumv.reasonloginsuccess[props.ag_platform]} | The SDK logged in the Signaling system successfully. |
+| {enumv.reasonrejected[props.ag_platform]} | The SDK is rejected by the server. |
+| {enumv.reasonlost[props.ag_platform]} | The SDK lost the connection with the Signaling system. |
+| {enumv.reasoninterrupt[props.ag_platform]} | The connection was interrupted. |
+| {enumv.reasonlogout[props.ag_platform]} | The SDK logged out of the Signaling system. |
+| {enumv.reasonban[props.ag_platform]} | The SDK is banned by the server. |
+| {enumv.reasonsameuid[props.ag_platform]} | The same user ID was used to join the same channel on different devices. |
+| {enumv.reasontokenexpired[props.ag_platform]} | The used token expired. You need to request a new token from your server. |
+
+
+### SDK Connection States
+
+| Value | Description |
+| :----------: | ---------------------------------------------------------- |
+| {enumv.sdkdisconnect[props.ag_platform]} | The SDK has disconnected with the server. |
+| {enumv.sdkconnecting[props.ag_platform]} | The SDK disconnected with the server. |
+| {enumv.sdkconnected[props.ag_platform]} | The SDK has connected with the server. |
+| {enumv.sdkreconnect[props.ag_platform]} | The connection is lost. The SDK is reconnecting with the server. |
+| {enumv.sdkfailed[props.ag_platform]} | The SDK failed to connect with the server. |
+
+
+### End-to-End Encryption Modes
+
+| Value | Description |
+| :----------: | ---------------------------------------------------------- |
+| {enumv.encryptionmodenone[props.ag_platform]} | No encryption. |
+| {enumv.aesonetwoeight[props.ag_platform]} | AES-128-GCM mode. |
+| {enumv.aestwofivesix[props.ag_platform]} | AES-256-GCM mode. |
+
+
+### Lock Event Types
+
+| Value | Description |
+| :----------: | ---------------------------------------------------------- |
+| {enumv.locktypesnap[props.ag_platform]} | The snapshot of the lock when the user joined the channel. |
+| {enumv.locktypeset[props.ag_platform]} | The lock is set. |
+| {enumv.locktyperemoved[props.ag_platform]} | he lock is removed. |
+| {enumv.locktypeacquired[props.ag_platform]} | The lock is acquired. |
+| {enumv.locktypereleased[props.ag_platform]} | The lock is released. |
+| {enumv.locktypeexpired[props.ag_platform]} | The lock expired. |
+
+
+### Log Output Levels
+
+| Value | Description |
+| :----------: | ---------------------------------------------------------- |
+| {enumv.loglevelnone[props.ag_platform]} | No log. |
+| {enumv.loglevelinfo[props.ag_platform]} | Output the log at the `error`, `warn`, or `info` level. Agora recommends you set to this value. |
+| {enumv.loglevelwarn[props.ag_platform]} | Output the log at the `error` or `warn` level. |
+| {enumv.loglevelerror[props.ag_platform]} | Output the log at the `error` level. |
+| {enumv.logleveldebug[props.ag_platform]} | Output the log at all levels. |
+
+### Message Types
+
+| Value | Description |
+| :----------: | ---------------------------------------------------------- |
+| {enumv.binarymessage[props.ag_platform]} | Binary type. |
+| {enumv.stringmessage[props.ag_platform]} | String type. |
+
+### Presence Event Types
+
+| Value | Description |
+| :----------: | ---------------------------------------------------------- |
+| {enumv.presencetypesnap[props.ag_platform]} | When users joined a channel for the first time, they received a snapshot of the channel pushed by the server. |
+| {enumv.presencetypeint[props.ag_platform]} | When users in the channel reach the limit, the event notifications are sent at intervals rather than in real time. |
+| {enumv.presencetypejoin[props.ag_platform]} | A remote user joined the channel. |
+| {enumv.presencetypeleave[props.ag_platform]} | A remote user left the channel. |
+| {enumv.presencetypetimeout[props.ag_platform]} | A remote user's connection timed out. |
+| {enumv.presencetypechange[props.ag_platform]} | A remote user's temporary state changed. |
+| {enumv.presencetypeout[props.ag_platform]} | The user did not enable presence when joining the channel. |
+
+### Storage Event Types
+
+| Value | Description |
+| :----------: | ---------------------------------------------------------- |
+| {enumv.storagetypeuser[props.ag_platform]} | User metadata event. |
+| {enumv.storagetypechannel[props.ag_platform]} | Channel metadata event. |
+
+### Topic Event Types
+
+| Value | Description |
+| :----------: | ---------------------------------------------------------- |
+| {enumv.topictypesnap[props.ag_platform]} | The snapshot of the topic when the user joined the channel. |
+| {enumv.topictypejoin[props.ag_platform]} | A remote user joined the channel. |
+| {enumv.topictypeleave[props.ag_platform]} | A remote user left the channel. |
\ No newline at end of file
diff --git a/shared/signaling/reference/api-ref/_lock-en.javascript.mdx b/shared/signaling/reference/api-ref/_lock-en.javascript.mdx
new file mode 100644
index 000000000..66a6e0021
--- /dev/null
+++ b/shared/signaling/reference/api-ref/_lock-en.javascript.mdx
@@ -0,0 +1,357 @@
+import * as lock from './shared/_lock.mdx'
+import * as config from './shared/_configuration.mdx'
+import * as enumv from './shared/_enumv.mdx'
+import Status from './shared/_rtmstatus.mdx'
+
+
+A critical resource can only be used by one process at a time. If a critical resource is shared between different processes, each process needs to adopt a mutually exclusive method to prevent mutual interference. Signaling provides a full set of lock solutions. By controlling different processes in a distributed system, you can solve the competition problem when users access shared resources.
+
+The client is able to set, remove, and revoke locks. We recommend that you control the permissions of the these operations on the client side based on your business needs.
+
+### {lock.set[props.ag_platform]}
+
+#### Description
+
+You need to configure the lock name, time to live (TTL) and other parameters by calling the {lock.set[props.ag_platform]} method. If the configuration succeeds, all users in the channel receives the {config.onlockevent[props.ag_platform]} event notifications of the {enumv.locktypeset[props.ag_platform]} type. For details, see [Event Listeners](#event-listeners).
+
+#### Method
+
+You can call the {lock.set[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.lock.setLock(
+ channelName: string,
+ channelType: string,
+ lockName: string,
+ options?: {
+ ttl?: number;
+ }
+): Promise;
+```
+
+| Parameter | Type | Required | Default| Description |
+| :-----------: | :----: | :------: | :----: | :-------------------------------- |
+| `channelName` | string | Required | - | Channel name. |
+| `channelType` | string | Required | - | Channel type. For details, see [Channel Types](#channel-types). |
+| `lockName` | string | Required | - | Lock name. |
+| `options` | object | Optional | - | Options for setting locks. |
+
+`options` contains the following properties:
+
+| Property | Type | Required | Default| Description |
+| :---: | :----: | :------: | :----: | :----------------------------------------------------------------------------------------------- |
+| `ttl` | number | Optional | `10` | Time to live in seconds of the lock, and the value range is [10,300]. When the user who owns the lock goes offline, if the user returns to the channel within the time they can still use the lock; otherwise, the lock is released and the users who listen for the {config.onlockevent[props.ag_platform]} event receives the {enumv.locktypereleased[props.ag_platform]} event. |
+
+#### Basic Usage
+
+```javascript
+try{
+ const result = await rtm.Lock.setLock(
+ channel:"my_channel",
+ channelType:"STREAM",
+ lockName: "my_lock",
+ { ttl: 30 }
+ );
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+#### Return Value
+
+If the method call succeeds, the {lock.setlockresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type SetLockResponse = {
+ timeToken: number , // Reserved property, indicating the timestamp of the successful operation.
+ channelName : string , // Channel name.
+ channelType : string , // Channel type.
+ lockName : string // Lock name.
+}
+```
+
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
+
+
+### {lock.acquire[props.ag_platform]}
+
+#### Description
+
+After setting a lock, you can call the {lock.acquire[props.ag_platform]} method on the client to acquire the right to own the lock. When you acquire the lock, other users in the channel receives the {config.onlockevent[props.ag_platform]} event of the {enumv.locktypeacquired[props.ag_platform]} type. For details, see Event Listener.
+
+#### Method
+
+You can call the {lock.acquire[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.lock.acquireLock(
+ channelName: string,
+ channelType: string,
+ lockName: string,
+ options?: { retry?: boolean }
+): Promise;
+```
+
+| Parameter | Type | Required | Default| Description |
+| :-----------: | :----: | :------: | :----: | :-------------------------------- |
+| `channelName` | string | Required | - | Channel name. |
+| `channelType` | string | Required | - | Channel type. For details, see [Channel Types](#channel-types). |
+| `lockName` | string | Required | - | Lock name. |
+| `options` | object | Optional | - | Options for setting locks. |
+
+`options` contains the following properties:
+
+| Property | Type | Required | Default | Description |
+| :-----: | :-----: | :------: | :-----: | :---------------------------------------------------------- |
+| `retry` | boolean | Optional | `false` | If the lock acquisition fails, whether to retry until the acquisition succeeds or the user leaves the channel. |
+
+#### Basic Usage
+
+```javascript
+try{
+ const result = await rtm.Lock.acquireLock(
+ "chat_room",
+ "STREAM",
+ "my_lock",
+ {retry:false}
+ );
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+#### Return Value
+
+If the method call succeeds, the {lock.acquirelockresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type AcquireLockResponse = {
+ timeToken: number , // Reserved property, indicating the timestamp of the successful operation.
+ channelName : string , // Channel name.
+ channelType : string , // Channel type.
+ lockName : string // Lock name.
+}
+```
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
+
+
+### {lock.release[props.ag_platform]}
+
+#### Description
+
+When a user no longer needs to own a lock, the user can call the {lock.release[props.ag_platform]} method on the client side to release the lock. When the lock is released, other users in the channel receives the {config.onlockevent[props.ag_platform]} event of {enumv.locktypereleased[props.ag_platform]} type. For details, see Event Listeners.
+
+At this time, if other users want to acquire the lock, they can call the {lock.acquire[props.ag_platform]} method on the client side to compete. New users acquiring locks have the same contention priority as the users who set the `retry` property to automatically retry to acquire locks.
+
+#### Method
+
+You can call the {lock.acquire[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.lock.releaseLock(
+ channelName: string,
+ channelType: string,
+ lockName: string
+): Promise;
+```
+
+| Property | Type | Required | Default| Description |
+| :-----------: | :----: | :------: | :----: | :------------------------------------------------------------------------------------------------------------ |
+| `channelName` | string | Required | - | Channel name. |
+| `channelType` | string | Required | - | Channel type. For details, see [Channel Types](#channel-types). |
+| `lockName` | string | Required | - | Lock name. |
+
+
+#### Basic Usage
+
+```javascript
+try{
+ const result = await rtm.lock.releaseLock(
+ "chat_room",
+ "STREAM", "my_lock"
+ );
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+#### Return Value
+
+If the method call succeeds, the {lock.releaselockresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type ReleaseLockResponse = {
+ timeToken: number , // Reserved property, indicating the timestamp of the successful operation.
+ channelName : string , // Channel name.
+ channelType : string , // Channel type.
+ lockName : string // Lock name.
+}
+```
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
+
+
+### {lock.revoke[props.ag_platform]}
+
+#### Description
+
+You may need to take back a lock owned by another user and allow the other users to acquire the lock. You can call the {lock.revoke[props.ag_platform]} method to revoke the lock. When the lock is revoked, all users in the channel receives the {config.onlockevent[props.ag_platform]} event of the {enumv.locktypereleased[props.ag_platform]} type. For details, see Event Listeners.
+
+At this time, if other users want to acquire the lock, they can call the {lock.acquire[props.ag_platform]} method on the client side to compete. New users acquiring locks have the same contention priority as the users who set the `retry` property to automatically retry to acquire locks.
+
+#### Method
+
+You can call the {lock.revoke[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.lock.revokeLock(
+ channelName: string,
+ channelType: string,
+ lockName: string,
+ owner: string
+): Promise;
+```
+
+| Property | Type | Required | Default| Description |
+| :-----------: | :----: | :------: | :----: | :-------------------------------- |
+| `channelName` | string | Required | - | Channel name. |
+| `channelType` | string | Required | - | Channel type. For details, see [Channel Types](#channel-types). |
+| `lockName` | string | Required | - | Lock name. |
+| `owner` | string | Required | - | User ID of the lock owner. |
+
+
+#### Basic Usage
+
+```javascript
+try{
+ const result = await rtm.lock.revokeLockLock(
+ "chat_room",
+ "STREAM",
+ "my_lock",
+ "Tony"
+ );
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+#### Return Value
+
+If the method call succeeds, the {lock.revokelockresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type RevokeLockResponse = {
+ timeToken: number , // Reserved property, indicating the timestamp of the successful operation.
+ channelName : string , // Channel name.
+ channelType : string , // Channel type.
+ lockName : string // Lock name.
+}
+```
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
+
+
+### {lock.get[props.ag_platform]}
+
+#### Description
+
+To query the lock information such as total number of locks, lock name, and lock user, time to live, call the {lock.get[props.ag_platform]} method on the client.
+
+#### Method
+
+You can call the {lock.get[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.lock.getLock(
+ channelName: string,
+ channelType: string
+): Promise;
+```
+
+| Property | Type | Required | Default| Description |
+| :-----------: | :----: | :------: | :----: | :------------------------------------------------------------------------------------------------------------ |
+| `channelName` | string | Required | - | Channel name. |
+| `channelType` | string | Required | - | Channel type. For details, see [Channel Types](#channel-types). |
+
+
+#### Basic Usage
+
+```javascript
+try{
+ const result = await rtm.lock.getLock("chat_room","STREAM");
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+#### Return Value
+
+If the method call succeeds, the {lock.getlockresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type GetLockResponse = {
+ timeToken: number , // Reserved property, indicating the timestamp of the successful operation.
+ channelName : string , // Channel name.
+ channelType : string , // Channel type.
+ totalLocks : string, // Total lock number.
+ lockDetails : [{
+ lockName: string, // Lock name.
+ owner: string, // Lock owner.
+ ttl: number // Time to live in seconds of the lock.
+ }]
+}
+```
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
+
+
+### {lock.remove[props.ag_platform]}
+
+#### Description
+
+Remove the lock by calling the {lock.remove[props.ag_platform]} method. When the lock is successfully removed, all the users in the channel receives the {config.onlockevent[props.ag_platform]} event notification in the {enumv.locktyperemoved[props.ag_platform]} type. For details, see Event Handlers.
+
+#### Method
+
+You can call the {lock.remove[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.lock.removeLock(
+ channelName: string,
+ channelType: string,
+ lockName: string
+): Promise;
+```
+
+| Property | Type | Required | Default| Description |
+| :-----------: | :----: | :------: | :----: | :-------------------------------- |
+| `channelName` | string | Required | - | Channel name. |
+| `channelType` | string | Required | - | Channel type. For details, see [Channel Types](#channel-types). |
+| `lockName` | string | Required | - | Lock name. |
+
+#### Basic Usage
+
+```javascript
+try{
+ const result = await rtm.Lock.removeLockLock("chat_room","STREAM", "my_lock");
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+#### Return Value
+
+If the method call succeeds, the {lock.removelockresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type RemoveLockResponse = {
+ timeToken: number , // Reserved property, indicating the timestamp of the successful operation.
+ channelName : string , // Channel name.
+ channelType : string , // Channel type.
+ lockName : string, // Lock name.
+}
+```
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
\ No newline at end of file
diff --git a/shared/signaling/reference/api-ref/_message-en.javascript.mdx b/shared/signaling/reference/api-ref/_message-en.javascript.mdx
new file mode 100644
index 000000000..6a6864bdb
--- /dev/null
+++ b/shared/signaling/reference/api-ref/_message-en.javascript.mdx
@@ -0,0 +1,104 @@
+import * as enumv from './shared/_enumv.mdx'
+import * as config from './shared/_configuration.mdx'
+import * as channel from './shared/_channel.mdx'
+import * as message from './shared/_message.mdx'
+import * as topic from './shared/_topic.mdx'
+import Status from './shared/_rtmstatus.mdx'
+import PublishOptions from './shared/_publishoptions.mdx';
+
+Sending and receiving messages is the most basic function of the Signaling service. Any message sent by the Signaling server can be delivered to any online subscribing user within 100 ms. Depending on your business requirements, you can send messages to one user only or broadcast messages to multiple users.
+
+Signaling offers two types of channels: message channels and stream channels. These channel types have the following differences in how messages are transmitted and methods are called:
+
+- Message Channel: The real-time channel. Messages are transmitted through the channel, and the channel is highly scalable. Local users can call the {message.publish[props.ag_platform]} method to send messages in the channel, and remote users can call the {channel.subscribe[props.ag_platform]} method to subscribe to the channel and receive messages.
+- Stream Channel: The streaming transmission channel. Messages are transmitted through the topic. Users need to join a channel first, and then join a topic. Local users can call the {topic.publish[props.ag_platform]} method to send messages in the topic, and remote users can call the {topic.subscribe[props.ag_platform]} method to subscribe to the topic and receive messages.
+
+This page introduces how to send and receive messages in a Message Channel.
+
+### {message.publish[props.ag_platform]}
+
+#### Description
+
+You can directly call the {message.publish[props.ag_platform]} method to send messages to all online users who subscribe to the channel. Even if you do not subscribe to a channel, you can still send messages in the channel.
+
+The following practices can effectively improve the reliability of message transmission:
The message payload should be within 32 KB; otherwise, the sending will fail.
The upper limit of the rate at which messages are sent to a single channel is 30 QPS. If the sending rate exceeds the limit, some messages will be discarded. A lower rate is better, as long as the requirements are met.
Signaling does not guarantee that the order in which all subscribers receive messages is the same as the order in which the sender sends messages. To ensure that messages are strictly ordered, Agora suggests that you customize the message sequence number in the message payload.
+
+After successfully calling this method, the SDK triggers a {config.onmessageevent[props.ag_platform]} event notification. Users who subscribe to the channel and enabled the event listener can receive this event notification. For details, see Event Listener.
+
+#### Method
+
+You can call the {message.publish[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.publish(
+ channelName: string,
+ message: string | Uint8Array,
+ options?: {customType?: string;}
+): Promise;
+```
+
+| Parameter | Type | Required | Default | Description |
+| :-----------: | :---------------: | :------: | :-----: | :----------------------------------------------------------- |
+| `message` | string \| Uint8Array | Required | - | The message payload. Supports string or Uint8Array type. |
+| `channelName` | string | Required | - | The channel name. You can only send messages to one channel at a time. |
+| `options` | object | Optional | - | The message options. |
+
+The `options` object includes the following property:
+
+| Property | Type | Required | Default | Description |
+| :----------: | :----: | :------: | :-----: | :----------------------------------------------- |
+| `customType` | string | Optional | - | A user-defined field. Only supports string type. |
+
+#### Basic usage
+
+Example 1: Send string messages to a specified channel.
+
+```javascript
+try {
+ const result = await rtm.publish( 'my_channel', "Hello world" );
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+Example 2: Send Uint8Array messages to a specified channel.
+
+```javascript
+const str2ab = function(str) {
+ var buf = new ArrayBuffer(str.length * 2); // Each character occupies 2 bytes.
+ var bufView = new Uint16Array(buf);
+ for (var i = 0, strLen = str.length; i < strLen; i++) {
+ bufView[i] = str.charCodeAt(i);
+ }
+ return buf;
+};
+var Message=str2ab('hello world');
+try {
+ const result = await rtm.publish('my_channel', Message );
+ console.log(result);
+} catch (status) {
+ console.log(status);
+}
+```
+
+After successfully calling this method, the SDK triggers a {config.onmessageevent[props.ag_platform]} event notification. Users who subscribe to the channel and enable event listener can receive this event notification. For details, see Event Listener.
+
+#### Return value
+
+If the method call succeeds, the {message.publishresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type PublishResponse = {
+ timeToken: number , // Reserved property, indicating the timestamp of the successful operation.
+ chanelName : string // Channel name.
+}
+```
+
+If the method call fails, an [`ErrorInfo`](#errorinfo) response is returned.
+
+### Receive
+
+Signaling provides event notifications for messages, states, and event changes. By setting event listeners, you can receive messages and events in subscribed channels.
+
+For information on how to add and set event listeners, see Event Listener.
\ No newline at end of file
diff --git a/shared/signaling/reference/api-ref/_presence-en.javascript.mdx b/shared/signaling/reference/api-ref/_presence-en.javascript.mdx
new file mode 100644
index 000000000..2b4faf0bc
--- /dev/null
+++ b/shared/signaling/reference/api-ref/_presence-en.javascript.mdx
@@ -0,0 +1,272 @@
+import * as presence from './shared/_presence.mdx'
+import * as storage from './shared/_storage.mdx'
+import * as config from './shared/_configuration.mdx'
+import * as enumv from './shared/_enumv.mdx'
+import Status from './shared/_rtmstatus.mdx'
+import Stateitem from './shared/_stateitem.mdx'
+
+
+The presence feature provides the ability to monitor user online, offline, and user historical state change. With the Presence feature, you can get real-time access to the following information:
+
+- Real-time event notification when a user joins or leaves a specified channel.
+- Real-time event notification when the custom temporary user state changes.
+- Query which channels a specified user has joined or subscribed to.
+- Query which users have joined a specified channel and their temporary user state data.
+
+
+
+**Presence** applies to both message channels and stream channels.
+
+
+
+### {presence.whonow[props.ag_platform]}
+
+#### Description
+
+Call the {presence.whonow[props.ag_platform]} method to query user information in real time such as the number of online users, the list of online users and their temporary user status in the specified channel.
+
+#### Method
+
+You can call the {presence.whonow[props.ag_platform]} method in the following way:
+
+```javascript
+rtm.presence.getOnlineUsers(
+ channelName: string,
+ channelType: string,
+ options?: object
+): Promise;
+```
+
+| Parameter | Type | Required | Default | Description |
+| :-----------: | :----: | :------: | :----: | :-------------------------------- |
+| `channelName` | string | Required | - | Channel name. |
+| `channelType` | string | Required | - | Channel type. For details, see [Channel Types](#channel-types). |
+| `options` | object | Optional | - | Query options. |
+
+options contains the following properties:
+
+| Property | Type | Required | Default | Description |
+| :--------------: | :-----: | :------: | :-----: | ------------------------------------------------------------------------------------- |
+| `includedUserId` | boolean | Optional | `true` | Whether the returned result contains the user ID of online users. |
+| `includedState` | boolean | Optional | `false` | Whether the returned result contains the temporary user state of online users. |
+| `page` | string | Optional | - | Page number of the returned result. If you do not provide this property, the SDK returns the first page by default. You can check whether there is next page in the `nextPage` property of the returned result. |
+
+#### Basic Usage
+
+```javascript
+const options = {
+ includeUserId : true ,
+ includeState : true,
+ page : "yourBookMark"
+}
+try{
+ const result = await rtm.presence.getOnlineUsers( "chat_room", 'MESSAGE', options );
+ console.log(result);
+} catch(status){
+ console.log(status);
+}
+```
+
+#### Return Value
+
+If the method call succeeds, the {presence.whonowresulttype[props.ag_platform]} response as follows is returned:
+
+```js
+type getOnlineUsersResponse = {
+ timeToken: number , // Reserved property, indicating the timestamp of the successful operation.
+ totalOccupancy : number , // Number of the online users in the channel.
+ occupants : Array
diff --git a/shared/signaling/release-notes/windows.mdx b/shared/signaling/release-notes/windows.mdx
deleted file mode 100644
index 6f67db789..000000000
--- a/shared/signaling/release-notes/windows.mdx
+++ /dev/null
@@ -1,388 +0,0 @@
-
-
-
-This page contains information about the following releases:
-
-## v1.5.1
-
-v1.5.1 was released on September 1, 2022 and improved the stability of the SDK.
-
-### v1.5.0
-v1.5.0 was released on July 27, 2022.
-
-#### Sunset features
-Image or file messages, historical messages, and offline messages are deprecated as of this release. If you have integrated these features in a previous release, you can continue to use them.
-
-#### API changes
-
-The following methods are deprecated:
-
-- createFileMessageByMediaId
-- createFileMessageByUploading
-- createImageMessageByMediaId
-- createImageMessageByUploading
-- cancelMediaUpload
-- cancelMediaDownload
-- downloadMediaToMemory
-- downloadMediaToFile
-- enableOfflineMessaging
-- enableHistoricalMessaging
-- onFileMessageReceived
-- onFileMessageReceivedFromPeer
-- onImageMessageReceived
-- onImageMessageReceivedFromPeer
-- onMediaUploadingProgress
-- onMediaDownloadingProgress
-
-### v1.4.10
-v1.4.10 was released on March 01, 2022.
-
-
-#### Issues fixed
-
-Fixed some rich media related crashes.
-### v1.4.9
-v1.4.9 was released on November 26, 2021.
-
-#### Improvements
-
-Improved the login success rate and connection stability under the IPv4/IPv6 dual-stack network.
-
-## v1.4.7
-v1.4.7 was released on July 19, 2021.
-
-Improved the success rate of logins and message delivery for the in poor network conditions.
-
-## v1.4.6
-
-v1.4.6 was released on June 21, 2021.
-
-Fixed some issues that may cause crashes to improve stability.
-
-## v1.4.5
-
-v1.4.5 was released on April 30, 2021.
-
-#### Improvements
-
-Fixed some issues that may cause crashes to improve stability.
-
-## v1.4.4
-
-v1.4.4 was released on April 20, 2021.
-
-#### Improvements
-
-Improved the success rate of logins and message delivery for the in poor network conditions.
-
-#### Issues fixed
-
-Fixed some issues that might cause the system to crash.
-
-## v1.4.3
-
-v1.4.3 was released on February 10, 2021.
-
-#### Issues fixed
-
-This release fixed the following issues:
-
-- Crash issues caused by occasional multi-thread access conflict.
-- High timeout rate for login operations.
-
-## v1.4.2
-
-v1.4.2 was released on November 23, 2020.
-
-**Improvements**
-
-- Improved performance of login and messaging operations.
-- Added the `AGORA_SDK_BOTH_RTM_AND_RTC` macro for the `RTM_AREA_CODE` enum type. You can use the macro to resolve naming conflicts when using and together.
-
-## v1.4.1
-
-v1.4.1 was released on September 30, 2020.
-
-**Improvements**
-
-Improved the log file.
-
-
-## v1.4.0
-
-v1.4.0 was released on September 1, 2020.
-
-**Compatibility changes**
-
-- You need to add new ports to the firewall whitelist. See [Firewall Requirements](../reference/firewall).
- - New TCP ports: 8443, 9136, 9137, 9141. New UDP port: 8443. Reason: Support real-time transport encryption.
- - New UDP ports: 8130, 9120, 9121. Reason: Support UDP path to improve the SDK's performance in sub-optimal network conditions.
- - New UDP port: 9700. Reason: Support data reporting.
-- In `setLogFileSize`, the default log file size increases from 512 KB to 10 MB. The maximum log file size increases from 10 MB to 1 GB.
-
-**New features**
-
-- Geofencing. You can call `setRtmServiceContext` to set the region of the Agora . The can only connect to Agora servers within the specified region.
-- Transport Layer Security (TLS) encryption.
-
-**Improvements**
-
-- Improved the success rate of login and message delivery of the in poor network conditions.
-- Optimized the reconnection mechanism.
-
-**API changes**
-
-#### New methods
-
-setRtmServiceContext
-
-## v1.3.0
-
-v1.3.0 was released on May 8, 2020.
-
-**Compatibility changes**
-
-- You need to add a new TCP port, 9140, to the firewall whitelist. The port is for sending and receiving image or file messages. See [Firewall Requirements](../reference/firewall).
-- The Agora server blocks any file message or image message that you send to 1.2.2 or earlier, or to the Signaling SDK.
-
-**New features**
-
-#### 1. Send and receive file messages
-
-v1.3.0 supports downloading non-empty files that are smaller than 30 MB. You can cancel an ongoing upload or download process at any time. Each file you upload to the Agora server corresponds to a media ID. The file stays on the Agora server for seven days. You can use the media ID to download the file as long as the it is still on the Agora server.
-
-You can only download files using the methods provided by the SDK.
-
-v1.3.0 adds the `IFileMessage` message class for saving and transferring a media ID. The `IFileMessage` class inherits from the `IMessage` class, so you can use the existing peer-to-peer or channel messaging methods to transfer the `IFileMessage` instance. You can use the `IFileMessage` object to complete the following tasks:
-
-- Set the filename and thumbnail of the uploaded file.
-- Get the size of the uploaded file.
-
-
The total size of the message content, the filename, and the thumbnail must not exceed 32 KB.
-
-#### 2. Send and receive image messages
-
-v1.3.0 supports downloading non-empty image files that are smaller than 30 MB. You can cancel an ongoing upload or download process at any time. Each image you upload to the Agora server corresponds to a media ID. The image file stays on the Agora server for seven days. You can use the media ID to download the image file as long as it is still on the Agora server.
-
-
You can only download image files using the download methods provided by the SDK.
-
-v1.3.0 adds the `IImageMessage` message class for saving and transferring a media ID. The `IImageMessage` class inherits from the `IMessage` class, so you can use the existing peer-to-peer or channel messaging methods to transfer the `IImageMessage` instance. You can use the `IImageMessage` object to complete the following tasks:
-
-- Set the filename and thumbnail of the uploaded image file.
-- Get the size of the uploaded image file.
-- Get the SDK-calculated width and height of image files in JPEG, JPG, BMP, or PNG format.
-- Set the width or height of image files. The width or height you set overwrites the width or height that the SDK calculates.
-
-
The total size of the message content, the filename, and the thumbnail must not exceed 32 KB.
-
-#### 3. Report the progress of upload or download
-
-The SDK returns the progress of an upload or download by callback every second during a task in progress. If the upload or download task pauses, the SDK ceases to return any further callback until the task continues.
-
-**Bug fixes**
-
-- Login failure caused by the SDK's incorrect identification of user's network type.
-- Other problems that may cause the system to crash.
-
-**API changes**
-
-#### New methods
-
-- createFileMessageByUploading
-- createImageMessageByUploading
-- cancelMediaUpload
-- cancelMediaDownload
-- createFileMessageByMediaId
-- createImageMessageByMediaId
-- downloadMediaToMemory
-- downloadMediaToFile
-
-#### New callbacks
-
-- onMediaUploadingProgress
-- onMediaDownloadingProgress
-- onMediaCancelResult
-- onFileMediaUploadResult
-- onImageMediaUploadResult
-- onFileMessageReceivedFromPeer
-- onImageMessageReceivedFromPeer
-- onFileMessageReceived
-- onImageMessageReceived
-- onMediaDownloadToMemoryResult
-- onMediaDownloadToFileResult
-
-#### Deprecated methods
-
-sendMessage is deprecated. Use sendMessage (const IMessage *message, const SendMessageOptions &options) instead.
-
-## v1.2.2
-
-v1.2.2 was released on December 13, 2019.
-
-
-**Compatibility Changes**
-
-This release deprecates the createAgoraService method, which creates an IAgoraService instance, and the initialize method, which initializes the created IAgoraService instance. As of this relase, you only need to call the createRtmService method and the initialize method to initialize it before being able to call its functions.
-
-
-**Issues Fixed**
-
-Occasionally fails to receive any callback after a channel attribute operation.
-
-## v1.2.1
-
-v1.2.1 was released on November 29, 2019.
-
-**New Feature**
-
-*Compatible with the endCall method of the Agora Signaling SDK*
-
-If you use the `sendMessageToPeer` method to send a text message in the format of AgoraRTMLegacyEndcallCompatibleMessagePrefix_\_\, then this method is compatible with the endCall method of the legacy Agora Signaling SDK. Replace \ with the channel ID from which you want to leave (end call), and replace \ with any additional information. Note that you must not put any "_" (underscore) in your additional information but you can set \ as empty "".
-
-**Issues Fixed**
-
-
-- The SDK fails to reconnect to the Agora system if the user disables VPN.
-- If a channel member reconnects to the Agora server after being interrupted, chances are the rest members of the channel can receive `onMemberJoined` twice.
-
-## v1.2.0
-
-v1.2.0 was released on November 6, 2019.
-
-**Compatibility Changes**
-
-Deprecated the `isOnline` property of the `PeerOnlineStatus`. Use `onlineState` instead.
-
-
-**New Features**
-
-#### Subscribe to the online status of the specified user(s)
-
-When the method call succeeds, the SDK returns the onPeersOnlineStatusChanged callback to report the online status of peers, to whom you subscribe.
-When the online status of the peers, to whom you subscribe, changes, the SDK returns the onPeersOnlineStatusChanged callback to report whose online status has changed.
-If the online status of the peers, to whom you subscribe, changes when the SDK is reconnecting to the server, the SDK returns the onPeersOnlineStatusChanged callback to report whose online status has changed when successfully reconnecting to the server.
-
-#### Unsubscribe from the online status of the specified user(s)
-
-Allows you to unsubscribe from the online status of the specified user(s).
-
-#### Get a list of the peers, to whose specific status you have subscribed
-
-Allows you to get a list of the peers, to whose specific status you have subscribed.
-
-#### Create a raw message
-
-Creates and initializes a raw message to be sent.
-
- If you set a text description, ensure that the size of the raw message and the description combined does not exceed 32 KB.
-
-
-
-
-
-
-## v1.1.0
-
-v1.1.0 was released on September 30, 2019. It added the following features:
-
-**Send an (offline) peer-to-peer message to a specified user**
-
-This version allows you to send a message to a specified user when that user is offline. If you set a message as an offline message and the specified user is offline when you send it, the server caches it. Please note that we only cache 200 offline messages for up to seven days for each receiver. If the number of the cached messages reaches this limit, the newest message overrides the oldest one.
-
-
-
-
-
-**Get the member count of specified channel(s)**
-
-Use the getChannelMemberCount method get the member count of specified channel(s) without the need to join them. One method call can get the member counts of a maximum of 32 channels.
-
-**Query the online status of the specified users**
-
-**User attribute operations**
-
-This version enables you to set or update a user's attributes. Please note:
-
-- Only after you successfully logging in the Agora system can you execute user attribute-related operations. Otherwise, the SDK triggers the `ATTRIBUTE_OPERATION_ERR_NOT_LOGGED_IN` error code.
-- The attributes you set will be cleared when you log out of .
-- You can only set a maximum of 16 KB attributes in a single method call. Otherwise, the SDK triggers the `ATTRIBUTE_OPERATION_ERR_SIZE_OVERFLOW` error code.
-
-You can do the following:
-
-- Substitute the local user's attributes with new ones.
-- Add or update the local user's attribute(s).
-- Use attribute keys to delete the local user's attributes.
-- Clear all attributes of the local user.
-- Get all attributes of a specified user.
-- Use attribute keys to get the attributes of a specified user.
-
-**Channel attribute operations**
-
-This version enables you to set or get the attribute(s) of a specified channel. You can use this feature to create group announcements.
-
-Each channel attribute exists as a key-value pair. See IRtmChannelAttribute for more information. Please note:
-
-- The key for each channel attribute must be printable characters and not exceed 8 KB.
-- Each channel attribute must not exceed 8 KB in length.
-- The overall size of the attributes of a channel must not exceed 32 KB.
-- The number of attributes of a channel must not exceed 32.
-
-You can do the following:
-
-- Substitute the attributes of a specified channel with new ones.
-- Adds or update the attribute of a specified channel.
-- Use attribute keys to deletes the attributes of a specified channel.
-- Clear all attributes of a specified channel.
-- Get all attributes of a specified channel.
-- Use attribute keys to get the attributes of a specified channel.
-
-> The enableNotificationToChannelMembers flag decides whether to notify all members of a channel about this attribute change.
-
-**Call invitation**
-
-This version enables you to create, send, cancel, accept, and decline a call invitation in a one-to-one or one-to-many voice/video call.
-
-**Join or leave a channel**
-
-**Send or receive channel messages**
-
-
-
-**Automatically return the latest number of members in the current channel**
-
-Once you are in a channel, you no longer must call the `getChannelMemberCount` method to get the member count of the current channel. We also do not recommend using `onMemberJoined` and `onMemberLeft` to keep track of the member counts. As of this release, the SDK returns to the channel members onMemberCountUpdated the latest channel member count when the number of channel members changes. Note that:
-
-- When the number of channel members ≤ 512, the SDK returns this callback when the number changes and at a MAXIMUM speed of once per second.
-- When the number of channel members exceeds 512, the SDK returns this callback when the number changes and at a MAXIMUM speed of once every three seconds.
-
-
-
-> Please treat this callback and the onGetMembers callback separately:
->
-> - The former is an automatic callback. It returns the current numer of channel members;
-> - The latter is triggered by the getMembers method. It returns a member list of the current channel. If the number of channel members exceeds 512, the SDK only returns a list of 512 randomly selected channel members.
-
-**Renew the Token**
-
-**Specify the default path to the SDK log file**
-
-Use the `setLogFile` method to change the default path to the SDK log file. To ensure that errors are completely written to the log file, we recommend calling this method immediately after you have created and initialized an `IRtmService` instance.
-
-
-
-**Set the output log level of the SDK**
-
-Use the `setLogFilter` method to set the output log level of the SDK. The log level follows the sequence of OFF, CRITICAL, ERROR, WARNING, and INFO. Choose a level to see the logs preceding that level. If, for example, you set the log level to WARNING, you see the logs within levels CRITICAL, ERROR, and WARNING (the OFF level does not return any log results). See also LOG_FILTER_TYPE.
-
-> You can call this method once you have created and initialized an `IRtmService` instance. You do not have to call this method until after calling the `login` method.
-
-**Set the log file size in KB**
-
-Use the `setLogFileSize` method to set the log file size. The log file has a default size of 512 KB. File size settings of less than 512 KB or greater than 10 MB will not take effect.
-
-> You can call this method once you have created and initialized an `IRtmService` instance. You do not have to call this method until after calling the `login` method.
-
-
-
-
-
\ No newline at end of file
diff --git a/shared/signaling/run-the-sample-project/android.mdx b/shared/signaling/run-the-sample-project/android.mdx
index bdcdba717..668af0b21 100644
--- a/shared/signaling/run-the-sample-project/android.mdx
+++ b/shared/signaling/run-the-sample-project/android.mdx
@@ -2,7 +2,7 @@
### 3. Integrate the
-Refer to the following steps to integrate the into the sample project:
+Refer to the following steps to integrate the into the reference app:
1. Download the [](https://github.com/AgoraIO/Signaling) repository on Github, and locate the `Agora-Signaling-Tutorial-Android` folder.
@@ -10,9 +10,9 @@ Refer to the following steps to integrate the into the sampl
3. Copy the `*.jar` file in the SDK folder into the `libs` directory of the project folder, and copy the `x86_64`, `x86`, `arm64-v8a`, and `armeabi-v7a` folders to `Agora-Signaling-Tutorial-Android\app\src\main\jniLibs`.
-### 4. Run the sample project
+### 4. Run the reference app
-Refer to the following steps to run the sample project:
+Refer to the following steps to run the reference app:
1. Open `app` with Android Studio. Android automatically syncs the project with gradle.
@@ -25,7 +25,7 @@ Refer to the following steps to run the sample project:
```
-3. Compile and run the sample project. Enter any string as the user ID (such as `userA`) and click **Login** to log into .
+3. Compile and run the reference app. Enter any string as the user ID (such as `userA`) and click **Login** to log into .
You need to specify the user ID yourself. The user ID supports the following character set:
diff --git a/shared/signaling/run-the-sample-project/cpp.mdx b/shared/signaling/run-the-sample-project/cpp.mdx
index 6796f423e..b6a7e7747 100644
--- a/shared/signaling/run-the-sample-project/cpp.mdx
+++ b/shared/signaling/run-the-sample-project/cpp.mdx
@@ -2,7 +2,7 @@
## 3. Integrate the
-Refer to the following steps to integrate the into the sample project:
+Refer to the following steps to integrate the into the reference app:
1. Download the [](https://github.com/AgoraIO/Signaling) repository on Github, and locate the `Agora-Signaling-Tutorial-Linux` folder.
@@ -10,9 +10,9 @@ Refer to the following steps to integrate the into the sampl
3. Copy the `*.so` file in the SDK folder into the `lib` directory of the project folder, and copy the `*.h` file in the SDK folder into the `include` directory of the project folder.
-## 4. Run the sample project
+## 4. Run the reference app
-Refer to the following steps to run the sample project:
+Refer to the following steps to run the reference app:
1. Go to the project directory, and run the following command to build the project:
```shellscript
diff --git a/shared/signaling/run-the-sample-project/ios.mdx b/shared/signaling/run-the-sample-project/ios.mdx
index 505f7fc38..878be7c56 100644
--- a/shared/signaling/run-the-sample-project/ios.mdx
+++ b/shared/signaling/run-the-sample-project/ios.mdx
@@ -2,7 +2,7 @@
### 3. Integrate the
-Refer to the following steps to integrate the into the sample project:
+Refer to the following steps to integrate the into the reference app:
1. Download the [](https://github.com/AgoraIO/Signaling) repository on Github, and locate the `Agora-Signaling-Tutorial-iOS-Swift` folder or the `Agora-Signaling-Tutorial-iOS-Objective-C` folder.
@@ -35,13 +35,13 @@ Refer to the following steps to integrate the into the sampl
4. Change the **Embed** property of `AgoraRtmKit.xcframework` to **Embed & Sign**.
-### 4. Run the sample project
+### 4. Run the reference app
-Refer to the following steps to run the sample project:
+Refer to the following steps to run the reference app:
1. (Agora--Tutorial-iOS-Swift) Enter your App ID in `AppId.swift`; (Agora--Tutorial-iOS-Objective-C) Enter your App ID in `AppId.m`.
-2. Compile and run the sample project.
+2. Compile and run the reference app.
3. Enter any string as the user ID (such as `userA`) and click **Login** to log in to .
diff --git a/shared/signaling/run-the-sample-project/java.mdx b/shared/signaling/run-the-sample-project/java.mdx
index 33190bf66..8fada7f6d 100644
--- a/shared/signaling/run-the-sample-project/java.mdx
+++ b/shared/signaling/run-the-sample-project/java.mdx
@@ -3,15 +3,15 @@
## 3. Integrate the
-Refer to the following steps to integrate the into the sample project:
+Refer to the following steps to integrate the into the reference app:
1. Download the [](https://github.com/AgoraIO/Signaling) repository on Github, and locate the `Agora-Signaling-Tutorial-Java` folder.
2. Download the latest version of [ (Linux Java)](../reference/downloads) and extract the files.
3. Copy the `*.jar` file and the `*.so` file in the SDK folder into the `lib` directory of the project folder.
-## 4. Run the sample project
+## 4. Run the reference app
-Refer to the following steps to run the sample project:
+Refer to the following steps to run the reference app:
1. Enter your App ID in `RtmJavaDemo.java`.
@@ -31,7 +31,7 @@ Refer to the following steps to run the sample project:
$ mvn package
```
-4. Run the sample project.
+4. Run the reference app.
```shell
$ java -cp target/Signaling-Client-Demo-1.0-SNAPSHOT.jar -Dsun.boot.library.path=lib/ io.agora.mainClass.RtmJavaDemo
diff --git a/shared/signaling/run-the-sample-project/macos.mdx b/shared/signaling/run-the-sample-project/macos.mdx
index 913d3c930..e1c006606 100644
--- a/shared/signaling/run-the-sample-project/macos.mdx
+++ b/shared/signaling/run-the-sample-project/macos.mdx
@@ -2,7 +2,7 @@
### 3. Integrate the
-Refer to the following steps to integrate the into the sample project:
+Refer to the following steps to integrate the into the reference app:
1. Download the [](https://github.com/AgoraIO/Signaling) repository on Github, and locate the `Agora-Signaling-Tutorial-macOS-Swift` folder.
@@ -30,13 +30,13 @@ Refer to the following steps to integrate the into the sampl
3. Change the **Embed** property of `AgoraRtmKit.framework` to **Embed & Sign**.
-### 4. Run the sample project
+### 4. Run the reference app
-Refer to the following steps to run the sample project:
+Refer to the following steps to run the reference app:
1. (Agora--Tutorial-macOS-Swift) Enter your App ID in `AppId.swift`.
-2. Compile and run the sample project.
+2. Compile and run the reference app.
3. Enter any string as the user ID (such as `userA`) and click **Login** to log in to .
diff --git a/shared/signaling/run-the-sample-project/unity.mdx b/shared/signaling/run-the-sample-project/unity.mdx
index 85acc63ed..ad500e2cb 100644
--- a/shared/signaling/run-the-sample-project/unity.mdx
+++ b/shared/signaling/run-the-sample-project/unity.mdx
@@ -20,7 +20,7 @@ For testing purposes, Agora Console supports generating tokens.
2. Fill in the App ID, App certificate, and user ID to log in to . You need to specify the user ID yourself (for example, "test"). The generated token is showed on the screen. When calling the `login` method later, ensure that the user ID is the same with the one that you use to generate the token.
-### 5. Configure the sample project
+### 5. Configure the reference app
#### Approach 1: Configure through the Agora asset package
@@ -60,13 +60,13 @@ For testing purposes, Agora Console supports generating tokens.
6. Find **Agora Properties** in Inspector list, and enter the App ID and the token.
-### 6. Run the sample project
+### 6. Run the reference app
-Run the sample project according to the following steps:
+Run the reference app according to the following steps:
1. Click the **File** menu bar in Unity, and set the device you need to run through **Build Settings**.
-2. Click the **Play** button at the top of the Unity main interface to run the sample project directly, or click **File** > **Build And Run** in the menu bar to compile and run the sample project. If the project runs successfully, you can see the following screen on your device:
+2. Click the **Play** button at the top of the Unity main interface to run the reference app directly, or click **File** > **Build And Run** in the menu bar to compile and run the reference app. If the project runs successfully, you can see the following screen on your device:
![1625553123694](https://web-cdn.agora.io/docs-files/1625553123694)
diff --git a/shared/signaling/run-the-sample-project/web.mdx b/shared/signaling/run-the-sample-project/web.mdx
index f235be16b..bd420bbde 100644
--- a/shared/signaling/run-the-sample-project/web.mdx
+++ b/shared/signaling/run-the-sample-project/web.mdx
@@ -21,9 +21,9 @@ For testing purposes, Agora Console supports generating tokens.
2. Fill in the App ID, App certificate, and user ID to log in to . You need to specify the user ID yourself (for example, "test"). The generated token is showed on the screen. When calling the `login` method later, ensure that the user ID is the same with the one that you use to generate the token.
-### 5. Install dependencies and run the sample project
+### 5. Install dependencies and run the reference app
-Refer to the following steps to integrate the into the sample project:
+Refer to the following steps to integrate the into the reference app:
1. Download the [](https://github.com/AgoraIO/Signaling) repository on Github, and locate the `Agora-Signaling-Tutorial-Web` folder.
@@ -31,7 +31,7 @@ Refer to the following steps to integrate the into the sampl
$ npm install
-3. Run the following command to run the sample project:
+3. Run the following command to run the reference app:
$ npm run dev
@@ -41,7 +41,7 @@ Refer to the following steps to integrate the into the sampl
## Expected result
-When the sample project runs successfully, you can see the following interface. You can enter the same App ID, Account Name (user ID) and temporary token to join the same channel and communicate with the remote user.
+When the reference app runs successfully, you can see the following interface. You can enter the same App ID, Account Name (user ID) and temporary token to join the same channel and communicate with the remote user.
![1618220582780](https://web-cdn.agora.io/docs-files/1618220582780)
diff --git a/shared/signaling/run-the-sample-project/windows.mdx b/shared/signaling/run-the-sample-project/windows.mdx
index b4546503f..5b305f376 100644
--- a/shared/signaling/run-the-sample-project/windows.mdx
+++ b/shared/signaling/run-the-sample-project/windows.mdx
@@ -2,7 +2,7 @@
### 3. Integrate the
-Refer to the following steps to integrate the into the sample project:
+Refer to the following steps to integrate the into the reference app:
1. Download the [](https://github.com/AgoraIO/Signaling) repository on Github, and locate the `Agora-Signaling-Tutorial-Windows` folder.
@@ -10,9 +10,9 @@ Refer to the following steps to integrate the into the sampl
3. Copy all files in `sdk` to `Agora-Signaling-Tutorial-Windows\SDK`.
-### 4. Run the sample project
+### 4. Run the reference app
-Refer to the following steps to run the sample project:
+Refer to the following steps to run the reference app:
1. Use to Visual Studio open `AgoraRTMTutorial.sln`.
diff --git a/shared/signaling/storage/_storage.mdx b/shared/signaling/storage/_storage.mdx
new file mode 100644
index 000000000..15c602ae2
--- /dev/null
+++ b/shared/signaling/storage/_storage.mdx
@@ -0,0 +1,62 @@
+import * as data from '@site/data/variables';
+import ProjectImplement from '@docs/shared/signaling/storage/project-implementation/index.mdx';
+import ProjectTest from '@docs/shared/signaling/storage/project-test/index.mdx';
+import Reference from '@docs/shared/signaling/storage/reference/index.mdx';
+
+ storage is a powerful feature that enables you to store and manage channel and user attributes in a serverless storage system. It acts as an extension of your database, offering synchronization across all participants. By using signaling storage, you can develop innovative, reliable, and scalable applications without the hassle of setting up your own database. This seamless integration ensures that data is readily available and up-to-date for all users, making it easier to build real-time and collaborative applications.
+
+Your receives metadata modification events in real-time enabling you to update the front-end accordingly. You can leverage user and channel metadata in apps to:
+
+* **Maintain search history**: Enable users to quickly search for specific messages, users, or topics within the app.
+
+* **Customize notifications**: Enable users to customize their notification settings, such as choosing notifications to receive and setting custom alert tones.
+
+* **Enhance security**: Enforce security measures, such as verifying user identity and encrypting messages.
+
+## Understand the tech
+
+Using storage you associate metadata with a particular channel or a specific user:
+
+* **Channel metadata**: store and distribute contextual channel data in your , such as props, announcements, member lists, and relationship chains.
+When channel properties are set, updated, or deleted by a user, an event notification is triggered, and other users in the channel receive this information within 100ms.
+
+* **User metadata**: store and distribute contextual data about users in your . A user has one
+set of metadata with one or more attributes. When an attribute is set, updated or deleted, an event notification is
+triggered, and users who subscribe to this users metadata will receive this information within 100ms.
+
+This section presents an overview of the steps required to integrate storage into your . The following figure shows the basic workflow you implement to read and write channel and user metadata:
+
+![Signaling workflow](/images/signaling/signaling-metadata-workflow.svg)
+
+
+**The implementation is coming soon.**
+
+
+
+
+## Prerequisites
+
+To follow this page, you must have:
+
+- Setup the [ reference app](/en/signaling/get-started/get-started-sdk#project-setup).
+
+
+## Implement storage
+
+In the storage reference app, after the local user logs in to , you set their metadata in the form of key-value pairs. When the user modifies a value stored as metadata, you call the update method to save the new value. You show a list of all users currently in the channel. When the local user selects another user from the list, you retrieve and display their metadata. You subscribe to the metadata of remote users to be notified of changes.
+
+
+
+## Test storage
+
+This section explains how to run the `storage` reference app and test channel and user metadata features. To run the project, take the following steps:
+
+
+
+## Reference
+
+This section contains additional information that either supplements the content on this page or directs you to documentation that covers other aspects of this product.
+
+
+
+
diff --git a/shared/signaling/storage/project-implementation/android.mdx b/shared/signaling/storage/project-implementation/android.mdx
new file mode 100644
index 000000000..99183d028
--- /dev/null
+++ b/shared/signaling/storage/project-implementation/android.mdx
@@ -0,0 +1,340 @@
+
+
+### Implement the user interface
+
+To show a list of users in the channel and enable the local user to update their status, you add the following elements to the user interface:
+
+* A `TextView` to display a caption
+* A `Button` to switch the local user status
+* A `LinearLayout` inside a `ScrollView` to display a list of users in the channel and their status
+
+To update the UI, in `/app/res/layout/activity_main.xml`, replace the entire `...` block with the following:
+
+ ```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+
+### Handle the system logic
+
+This section describes the steps required to use the relevant libraries and declare the necessary variables.
+
+1. Import the required and Android libraries
+
+ To read and write metadata and manage UI elements, add the following statements after the last `import` statement in `/app/java/com.example./MainActivity`:
+
+ ```java
+ import io.agora.rtm.RtmMetadataItem;
+ import io.agora.rtm.RtmMetadataOptions;
+ import java.util.ArrayList;
+ import java.util.HashMap;
+ import android.graphics.Color;
+ import android.widget.LinearLayout;
+ ```
+1. Define variables to manage user `TextView`s and the user availability status
+
+ In `/app/java/com.example./MainActivity`, add the following declarations to the `MainActivity` class:
+
+ ```java
+ // Associate the TextView for each user with their user ID
+ private HashMap userTextViews
+ = new HashMap();
+ private boolean isUserBusy = false; // User status
+ ```
+
+### Read and write metadata
+
+To implement the use of metadata, take the following steps:
+
+1. **Set local user metadata**
+
+ After a user successfully logs in to , you clear unwanted metadata from a previous login, and add fresh metadata that you want to share with other users. To do this:
+
+ 1. Add the following method to the `MainActivity` class:
+
+ ```java
+ void setLocalUserMetaData() {
+ // Clear previous metadata
+ mRtmClient.clearLocalUserMetadata(new RtmMetadataOptions(), new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ // Create an ArrayList of RtmMetadataItems
+ ArrayList metadata = new ArrayList();
+ metadata.add(new RtmMetadataItem("email", uid + "@example.com"));
+ metadata.add(new RtmMetadataItem("gender", "F"));
+ metadata.add(new RtmMetadataItem("myStatus", isUserBusy ? "busy" : "available"));
+ // Set metadata options
+ RtmMetadataOptions options = new RtmMetadataOptions();
+ // Set local user metadata
+ mRtmClient.setLocalUserMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ writeToMessageHistory( "Added LocalUserMetadata");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ writeToMessageHistory( "addLocalUserMetadata failed, error: " + errorInfo.toString());
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ writeToMessageHistory("Failed to clear local user metadata " + errorInfo.toString());
+ }
+ });
+ }
+ ```
+
+ 1. Call `setLocalUserMetaData` after login succeeds.
+
+ In `onClickLogin`, add the following to the `onSuccess` callback of `mRtmClient.login`:
+
+ ```java
+ setLocalUserMetaData();
+ ```
+
+1. **Update local user metadata**
+
+ When the local user taps the button to switch their status, you update the user's metadata. To do this, add the following method to the `MainActivity` class:
+
+ ```java
+ public void changeMyStatus(View v){
+ TextView tv = (TextView) v;
+ isUserBusy = !isUserBusy; // Switch status
+ tv.setText(isUserBusy ? "Busy" : "Available"); // Change the button caption
+ ArrayList metadata = new ArrayList();
+ // Add a RtmMetadataItem to the ArrayList
+ metadata.add(new RtmMetadataItem("myStatus", isUserBusy ? "busy" : "available"));
+ RtmMetadataOptions options = new RtmMetadataOptions();
+ // Update metadata with the new value
+ mRtmClient.updateLocalUserMetadata(metadata, options,null);
+ }
+ ```
+
+1. **Handle the `onUserMetadataUpdated` callback**
+
+ When you receive notification of change in a user's metadata, you update the status of the user in the user list. In `onCreate`, replace the `onUserMetadataUpdated` method under `new RtmClientListener() {` with the following:
+
+ ```java
+ @Override
+ public void onUserMetadataUpdated(String userId, RtmMetadata rtmMetadata) {
+ updateUserInList(userId, rtmMetadata.items.get(0).getValue().equals("busy"));
+ }
+ ```
+
+1. **Show the list of users in a channel**
+
+ When the local user successfully joins a channel, you call `mRtmChannel.getMembers` to obtain a list of users in the channel and add each user to the user list. To do this:
+
+ 1. In the `MainActivity` class, add the following method:
+
+ ```java
+ private void updateChannelMemberList() {
+ // Retrieve a list of users in the channel
+ mRtmChannel.getMembers(new ResultCallback>() {
+ @Override
+ public void onSuccess(List rtmChannelMembers) {
+ for (int i = 0; i < rtmChannelMembers.size(); i++) {
+ RtmChannelMember member = rtmChannelMembers.get(i);
+ // Add the user to the user list
+ updateUserInList(member.getUserId(), false);
+ }
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+
+ }
+ });
+ }
+ ```
+
+ 1. Call `updateChannelMemberList` after the user successfully joins a channel
+
+ In `onClickJoin`, add the following to the `onSuccess` callback of `mRtmChannel.join`:
+
+ ```java
+ updateChannelMemberList();
+ ```
+
+1. **Display a user in the list**
+
+ To fill the channel user list, you create a `TextView` for each user. At this time, you subscribe to the user's metadata to receive notification of changes. To add or update an item in the user list:
+
+ 1. Add the following method to the `MainActivity` class:
+
+ ```java
+ void updateUserInList(String userID, boolean busy) {
+ TextView userTextView;
+ if (userTextViews.containsKey(userID)) {
+ // User already in the list, update the TextView
+ userTextView = userTextViews.get(userID);
+ } else {
+ // Create a new TextView
+ userTextView = new TextView(getBaseContext());
+ }
+
+ int iconUnicode = busy ? 0x1F6AB : 0x2705;
+ userTextView.setText(new String(Character.toChars(iconUnicode)) + " " + userID);
+
+ if (!userTextViews.containsKey(userID)) { // Add a new user to the list
+ // Subscribe to metadata change event for the user
+ mRtmClient.subscribeUserMetadata(userID, null);
+ // Set up the user's TextView
+ userTextView.setTag(userID);
+ userTextView.setPadding(10, 5, 5, 5);
+ userTextView.setOnClickListener(onUserClick);
+ userTextView.setBackgroundColor(Color.parseColor("#ADD8E6"));
+ // Add the TextView to the LinearLayout
+ runOnUiThread(() -> {
+ LinearLayout userList = findViewById(R.id.userList);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ params.setMargins(5, 5, 5, 5);
+ userTextViews.put(userID, userTextView); // Store a TextView reference for future updates
+ userList.addView(userTextView, params);
+ });
+ }
+ }
+ ```
+
+ 1. Add the following to the `onMemberJoined` callback in `onClickJoin`:
+
+ ```java
+ updateUserInList(member.getUserId(), false);
+ ```
+
+1. **Remove a user from the list**
+
+ When a remote user leaves the channel, you remove the corresponding `TextView` from the user list. To do this:
+
+ 1. Add the following method to the `MainActivity` class:
+
+ ```java
+ void removeUserFromList(String userID) {
+ TextView userTextView;
+ if (userTextViews.containsKey(userID)) {
+ // Retrieve the corresponding TextView
+ userTextView = userTextViews.get(userID);
+ LinearLayout userList = findViewById(R.id.userList);
+ // Remove TextView
+ runOnUiThread(() -> {
+ userList.removeView(userTextView);
+ });
+ userTextViews.remove(userID);
+ }
+ }
+ ```
+
+ 1. Add the following to the `onMemberLeft` callback in `onClickJoin`:
+
+ ```java
+ removeUserFromList(member.getUserId());
+ ```
+
+1. **Retrieve and show metadata for a selected user**
+
+ When the local user selects a user from the list, you retrieve metadata for that user and display the key-value pairs in a `Toast` message. To do this, add the following method to the `MainActivity` class:
+
+ ```java
+ View.OnClickListener onUserClick = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String userId = v.getTag().toString();
+ // Retrieve metadata for the selected user
+ mRtmClient.getUserMetadata(userId, new ResultCallback(){
+ @Override
+ public void onSuccess(RtmMetadata userMetadata){
+ // Concatenate keys and values in metadata items
+ StringBuilder sb = new StringBuilder("user: " +userId);
+ for (int j=0; j < userMetadata.items.size(); j++) {
+ RtmMetadataItem mdItem = userMetadata.items.get(j);
+ sb.append(", ").append(mdItem.getKey()).append(": ").append(mdItem.getValue());
+ }
+ runOnUiThread(() -> {
+ Toast toast = Toast.makeText(getApplicationContext(), sb.toString(), Toast.LENGTH_LONG);
+ toast.show();
+ });
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+
+ }
+ });
+ }
+ };
+ ```
+
+1. **Clear the user list when the local user leaves the channel**
+
+ When the local user leaves the channel, you clear the user list. To do this, in `onClickLeave`, add the following lines to the `onSuccess` callback of `mRtmChannel.leave`:
+
+ ```java
+ LinearLayout userList = findViewById(R.id.userList);
+ runOnUiThread(userList::removeAllViews);
+ userTextViews.clear();
+ ```
+
+1. **Clear metadata on logout**
+
+ When the local user logs out of , you clear the local user metadata. To do this, in `onClickLogout`, add the following lines to the `onSuccess` callback of `mRtmClient.logout`:
+
+ ```java
+ mRtmClient.clearLocalUserMetadata(new RtmMetadataOptions(),null);
+ ```
+
+
\ No newline at end of file
diff --git a/shared/signaling/storage/project-implementation/index.mdx b/shared/signaling/storage/project-implementation/index.mdx
new file mode 100644
index 000000000..b1dba59b0
--- /dev/null
+++ b/shared/signaling/storage/project-implementation/index.mdx
@@ -0,0 +1,8 @@
+import Android from './android.mdx';
+import Web from './web.mdx'
+import Ios from './ios.mdx';
+import Macos from './macos.mdx';
+
+
+
+
diff --git a/shared/signaling/storage/project-implementation/ios.mdx b/shared/signaling/storage/project-implementation/ios.mdx
new file mode 100644
index 000000000..170d386ef
--- /dev/null
+++ b/shared/signaling/storage/project-implementation/ios.mdx
@@ -0,0 +1,8 @@
+import Source from './swift.mdx';
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/storage/project-implementation/macos.mdx b/shared/signaling/storage/project-implementation/macos.mdx
new file mode 100644
index 000000000..74e608cdd
--- /dev/null
+++ b/shared/signaling/storage/project-implementation/macos.mdx
@@ -0,0 +1,8 @@
+import Source from './swift.mdx';
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/storage/project-implementation/swift.mdx b/shared/signaling/storage/project-implementation/swift.mdx
new file mode 100644
index 000000000..abe7a6c00
--- /dev/null
+++ b/shared/signaling/storage/project-implementation/swift.mdx
@@ -0,0 +1,339 @@
+
+### Implement the user interface
+
+To show a list of users in the channel and enable the local user to update their status, you add the following elements to the user interface:
+
+
+* A `UITextView` to display a caption
+* A `UIButton` to switch the local user status
+
+
+* An `NSTextView` to display a caption
+* An `NSButton` to switch the local user status
+
+
+To create this user interface, in the `ViewController` class:
+
+1. **Add the UI elements you need**
+
+ Add the following declarations at the top of the class:
+
+
+ ```swift
+ var statusText: UITextView!
+ var changeUserStatus: UIButton!
+ ```
+
+
+ ```swift
+ var statusText: NSTextView!
+ var changeUserStatus: NSButton!
+ ```
+
+
+1. **Configure the UI elements in your interface**
+
+ Paste the following lines inside the `initViews` function:
+
+
+ ```swift
+ statusText = UITextView(frame: CGRect(x: 40, y: 425, width: 330, height: 50))
+ statusText.text = "Set status:"
+ self.view.addSubview(statusText)
+
+ changeUserStatus = UIButton(type: .system)
+ changeUserStatus.frame = CGRect(x: 40, y: 480, width: 125, height: 30)
+ changeUserStatus.setTitle("Available", for: .normal)
+ changeUserStatus.addTarget(self, action: #selector(changeMyStatus), for: .touchUpInside)
+ self.view.addSubview(changeUserStatus)
+ ```
+
+
+ ```swift
+ statusText = NSTextView(frame: CGRect(x: 40, y: 425, width: 330, height: 50))
+ statusText.string = "Set status:"
+ self.view.addSubview(statusText)
+
+ changeUserStatus = NSButton()
+ changeUserStatus.frame = CGRect(x: 40, y: 480, width: 125, height: 30)
+ changeUserStatus.title = "Available"
+ changeUserStatus.target = self
+ changeUserStatus.action = #selector(changeMyStatus)
+ self.view.addSubview(changeUserStatus)
+ ```
+
+
+### Handle the system logic
+
+This section describes the steps required to use the relevant libraries and declare the necessary variables.
+
+1. Define variables to manage user `TextView`s and the user availability status
+
+ Add the following declarations to the top of the `ViewController` class:
+
+
+ ```swift
+ // Associate the UITextView for each user with their user ID
+ var userTextViews = [String: UITextView]()
+ var isUserBusy:Bool = false // User status
+ ```
+
+
+ ```swift
+ // Associate the NSTextView for each user with their user ID
+ var userTextViews = [String: NSTextView]()
+ var isUserBusy:Bool = false // User status
+ ```
+
+
+### Read and write metadata
+
+To implement the use of metadata, take the following steps
+
+1. **Set local user metadata**
+
+ After a user successfully logs in to , you clear unwanted metadata from a previous login, and add fresh metadata that you want to share with other users. To do this:
+
+ 1. Add the following method to the `ViewController` class:
+
+ ```swift
+ func setLocalUserMetaData() {
+ // Clear previous metadata
+ var options = AgoraRtmMetadataOptions()
+ let errorCode = await kit.clearLocalUserMetadata(options)
+ if errorCode == .ok {
+ // Create a list of RtmMetadataItems
+ var metadata = [AgoraRtmMetadataItem]()
+ metadata.append(AgoraRtmMetadataItem("email", "\(uid)@example.com"))
+ metadata.append(AgoraRtmMetadataItem("gender", "F"))
+ metadata.append(AgoraRtmMetadataItem("myStatus", isUserBusy ? "busy" : "available"))
+
+ // Set metadata options
+ var options = AgoraRtmMetadataOptions()
+ // Set local user metadata
+ let errorCode = await kit.setLocalUserMetadata(metadata, options)
+ if errorCode == .ok {
+ addMsgToRecord("Added LocalUserMetadata")
+ } else {
+ addMsgToRecord("addLocalUserMetadata failed, error: \(errorCode?.rawValue)")
+ }
+ } else {
+ addMsgToRecord("Failed to clear local user metadata \(errorCode?.rawValue)")
+ }
+ }
+ ```
+
+ 1. Call `setLocalUserMetaData` after login succeeds.
+
+ In the `login` function, add the following to the `errorCode == .ok` callback of `kit.login`:
+
+ ```swift
+ setLocalUserMetaData()
+ ```
+
+1. **Update local user metadata**
+
+ When the local user taps the button to switch their status, you update the user's metadata. To do this, add the following method to the `ViewController` class:
+
+ ```swift
+ @objc func changeMyStatus() {
+ isUserBusy = !isUserBusy // Switch status
+
+ var metadata = [AgoraRtmMetadataItem]()
+ // Add a RtmMetadataItem to the array
+ metadata.append(AgoraRtmMetadataItem("myStatus", isUserBusy ? "busy" : "available"))
+ var options = AgoraRtmMetadataOptions()
+ // Update metadata with the new value
+ kit.updateLocalUserMetadata(metadata, options)
+ }
+ ```
+
+1. **Update the UI after a change in a user's metadata**
+
+ When you receive notification of change in a user's metadata, you update the status of the user in the user list. To perform this, in `ViewController`, add the following function inside `extension ViewController: AgoraRtmDelegate` along with the existing event handlers:
+
+ ```swift
+ func rtmKit(_ kit: AgoraRtmKit, userMetadataUpdated userId: String, metadata data: AgoraRtmMetadata) {
+ updateUserInList(userID: userId, busy: data.items[0].getValue().equals("busy"))
+ }
+ ```
+
+1. **Show the list of users in a channel**
+
+ When the local user successfully joins a channel, you call `channel.getMembers` to obtain a list of users in the channel and add each user to the user list. To do this:
+
+ 1. In the `ViewController` class, add the following method:
+
+ TODO: Please add the correct swift function here
+ ```swift
+ private void updateChannelMemberList() {
+ // Retrieve a list of users in the channel
+ mRtmChannel.getMembers(new ResultCallback>() {
+ @Override
+ public void onSuccess(List rtmChannelMembers) {
+ for (int i = 0; i < rtmChannelMembers.size(); i++) {
+ RtmChannelMember member = rtmChannelMembers.get(i);
+ // Add the user to the user list
+ updateUserInList(member.getUserId(), false);
+ }
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+
+ }
+ });
+ }
+ ```
+
+ 1. Call `updateChannelMemberList` after the user successfully joins a channel
+
+ In `joinChannel`, add the following to the `.channelErrorOk` result of `channel?.join()`:
+
+ ```swift
+ updateChannelMemberList()
+ ```
+
+1. **Display a user in the list**
+
+ To fill the channel user list, you create a `TextView` for each user. At this time, you subscribe to the user's metadata to receive notification of changes. To add or update an item in the user list:
+
+ 1. Add the following method to the `ViewController` class:
+
+
+ ```swift
+ func updateUserInList(userID: String, busy: Bool) {
+ var userTextView: UITextView
+ if (userTextViews[userID] != nil) {
+ // User already in the list, update the TextView
+ userTextView = userTextViews[userID]!
+ } else {
+ // Create a new TextView
+ userTextView = UITextView()
+ }
+
+ let code = busy ? 0x1F6AB : 0x2705
+ var iconUnicode = UnicodeScalar(code)
+
+ userTextView.text = "\(iconUnicode) \(userID)"
+
+ if (userTextViews[userID] == nil) { // Add a new user to the list
+ // Subscribe to metadata change event for the user
+ kit.subscribeUserMetadata(userID)
+ // Set up the user's TextView
+ userTextView.tag = Int(userID)!
+ userTextView.textContainerInset = UIEdgeInsets(top: 10, left: 5, bottom: 5, right: 5)
+ userTextView.backgroundColor = UIColor.blue
+ userTextViews[userID] = userTextView // Store a TextView reference for future updates
+ self.view.addSubview(userTextView)
+ }
+ }
+ ```
+
+
+ ```swift
+ func updateUserInList(userID: String, busy: Bool) {
+ var userTextView: NSTextView
+ if (userTextViews[userID] != nil) {
+ // User already in the list, update the TextView
+ userTextView = userTextViews[userID]!
+ } else {
+ // Create a new TextView
+ userTextView = NSTextView()
+ }
+
+ let code = busy ? 0x1F6AB : 0x2705
+ var iconUnicode = UnicodeScalar(code)
+
+ userTextView.string = "\(iconUnicode) \(userID)"
+
+ if (userTextViews[userID] == nil) { // Add a new user to the list
+ // Subscribe to metadata change event for the user
+ kit.subscribeUserMetadata(userID)
+ // Set up the user's TextView
+ userTextView.backgroundColor = NSColor.blue
+ userTextViews[userID] = userTextView // Store a TextView reference for future updates
+ self.view.addSubview(userTextView)
+ }
+ }
+ ```
+
+
+ 1. Add the following to the `memberJoined` callback in `extension ViewController: AgoraRtmChannelDelegate {}`:
+
+ ```swift
+ updateUserInList(userID: memberJoined.userId, busy: false)
+ ```
+
+1. **Remove a user from the list**
+
+ When a remote user leaves the channel, you remove the corresponding `TextView` from the user list. To do this:
+
+ 1. Add the following method to the `ViewController` class:
+
+ ```swift
+ func removeUserFromList(userID: String) {
+ if (userTextViews[userID] != nil) {
+ // Remove TextView
+ userTextViews[userID] = nil
+ }
+ }
+ ```
+
+ 1. Add the following to the `memberLeft` callback in `extension ViewController: AgoraRtmChannelDelegate {}`:
+
+ ```swift
+ removeUserFromList(userID: memberLeft.userId)
+ ```
+
+1. **Retrieve and show metadata for a selected user**
+
+ When the local user selects a user from the list, you retrieve metadata for that user and display the key-value pairs in a `Toast` message. To do this, add the following method to the `ViewController` class:
+
+ TODO: Please add the correct swift function here
+ ```java
+ View.OnClickListener onUserClick = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String userId = v.getTag().toString();
+ // Retrieve metadata for the selected user
+ mRtmClient.getUserMetadata(userId, new ResultCallback(){
+ @Override
+ public void onSuccess(RtmMetadata userMetadata){
+ // Concatenate keys and values in metadata items
+ StringBuilder sb = new StringBuilder("user: " +userId);
+ for (int j=0; j < userMetadata.items.size(); j++) {
+ RtmMetadataItem mdItem = userMetadata.items.get(j);
+ sb.append(", ").append(mdItem.getKey()).append(": ").append(mdItem.getValue());
+ }
+ runOnUiThread(() -> {
+ Toast toast = Toast.makeText(getApplicationContext(), sb.toString(), Toast.LENGTH_LONG);
+ toast.show();
+ });
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+
+ }
+ });
+ }
+ };
+ ```
+
+1. **Clear the user list when the local user leaves the channel**
+
+ When the local user leaves the channel, you clear the user list. To do this, in `leaveChannel`, add the following lines to the leaving successful `errorCode == .ok` block of `channel?.leave()`:
+
+ ```swift
+ userTextViews.removeAll()
+ ```
+
+1. **Clear metadata on logout**
+
+ When the local user logs out of , you clear the local user metadata. To do this, in `logout`, add the following lines to the `errorCode == .ok` block of `kit?.logout()`:
+
+ ```swift
+ var options: AgoraRtmMetadataOptions = AgoraRtmMetadataOptions()
+ kit.clearLocalUserMetadata(options)
+ ```
diff --git a/shared/signaling/storage/project-implementation/web.mdx b/shared/signaling/storage/project-implementation/web.mdx
new file mode 100644
index 000000000..9d06b6019
--- /dev/null
+++ b/shared/signaling/storage/project-implementation/web.mdx
@@ -0,0 +1,242 @@
+
+
+The `storage` reference app implements the storage logic in the `SignalingManagerStorage` module defined in `/src/storage/signaling_manager_storage.js`. This module encapsulates the `SignalingManagerAuthentication` module from the [Secure authentication](../get-started/authentication-workflow) project, and adds storage functionality such as reading, writing, and updating metadata.
+
+The following code examples show how to implement storage features in your :
+
+### Set local user metadata
+
+ To set a key-value pair for the local user's metadata, you call `setUserMetadata`:
+
+ ```javascript
+ const setUserMetadata = async function (uid, key, value) {
+ // Define a data array to hold key-value pairs
+ const data = [
+ {
+ key: key, // Metadata Item's key
+ value: value, // Metadata Item's value
+ revision: -1, // Versioning switch on write operations:
+ // -1: Turn off version checking.
+ // > 0, The target revision number must match this value for the operation to succeed.
+ },
+ ];
+
+ const options = {
+ userId: uid, // current user's uid
+ majorRevision: -1, // Version control switch, -1 => Turn off version checking
+ addTimeStamp: true, // Whether to record timestamps of edits
+ addUserId: true, // Whether to log the editor's user ID
+ };
+
+ try {
+ await signalingManager.getSignalingEngine()
+ .storage.setUserMetadata(data, options);
+ messageCallback(`User metadata key ${key} saved`);
+ } catch (status) {
+ console.log(status);
+ }
+ };
+ ```
+
+### Update local user metadata
+
+ To update a value in the local user's metadata, call `updateUserMetadata`.
+
+ ```javascript
+ const updateUserMetadata = async function (uid, key, value) {
+ // Define a data array to hold key-value pairs
+ const data = [
+ {
+ key: key, // Metadata Item's key
+ value: value, // Metadata Item's value
+ revision: -1, // Versioning switch on write operations:
+ // -1: Turn off version checking
+ // > 0, The target revision number must match this value for the operation to succeed.
+ },
+ ];
+
+ const options = {
+ userId: uid, // current user's uid
+ majorRevision: -1, // Version control switch, -1 => Turn off version checking
+ addTimeStamp: true, // Whether to record timestamps of edits
+ addUserId: true, // Whether to log the editor's user ID
+ };
+
+ try {
+ await signalingManager.getSignalingEngine()
+ .storage.updateUserMetadata(data, options);
+ messageCallback(`User metadata key ${key} saved`);
+ } catch (status) {
+ console.log(status);
+ }
+ };
+ ```
+
+### Retrieve a user's metadata
+
+ To get all the key-value pairs stored in a user's metadata, call `getUserMetadata`:
+
+ ```js
+ const getUserMetadata = async function (uid) {
+ try {
+ const result = await signalingManager
+ .getSignalingEngine()
+ .storage.getUserMetadata({ userId: uid });
+ return result.metadata;
+ } catch (status) {
+ console.log(status);
+ }
+ };
+ ```
+
+### Subscribe to a user's metadata
+
+ To receive notifications of modifications to a user's data, you subscribe to the users metadata by calling `subscribeUserMetadata`:
+
+ ```js
+ const subscribeUserMetadata = async function (uid) {
+ try {
+ const result = await signalingManager
+ .getSignalingEngine()
+ .storage.subscribeUserMetadata(uid);
+ messageCallback("Subscribed to metadata events from " + uid);
+ } catch (status) {
+ console.log(status);
+ }
+ }
+ ```
+
+### Set channel metadata
+
+ To store a key-value pair in the channel's metadata, call `setChannelMetadata`:
+
+ ```js
+ const setChannelMetadata = async function (channelName, key, value, revision, lockName) {
+ // Define a data array to hold key-value pairs
+ const data = [
+ {
+ key: key, // Metadata Item's key
+ value: value, // Metadata Item's value
+ revision: revision, // Versioning switch on write operations:
+ // -1: Turn off version checking.
+ // > 0, The target revision number must match this value for the operation to succeed.
+ },
+ ];
+
+ const options = {
+ majorRevision: -1, // Use this field to enable version number verification of the entire set of channel attributes.
+ lockName: lockName, // When you specify a lock, only the user who calls the acquireLock method to acquire the lock can perform the operation.
+ addTimeStamp: true,
+ addUserId: true,
+ };
+
+ try {
+ const result = await signalingManager
+ .getSignalingEngine()
+ .storage.setChannelMetadata(channelName, "MESSAGE", data, options);
+ messageCallback(`channel metadata key '${key}' set successfully`);
+ } catch (status) {
+ messageCallback(`Error setting channel metadata: ${status.reason}`)
+ }
+ };
+ ```
+
+### Retrieve channel metadata
+
+ To get all the key-value pairs stored in a channels's metadata, call `getChannelMetadata`:
+
+ ```js
+ const getChannelMetadata = async function (channelName, channelType) {
+ try {
+ const result = await signalingManager
+ .getSignalingEngine()
+ .storage.getChannelMetadata(channelName, channelType);
+ return result.metadata;
+ } catch (status) {
+ console.log(status);
+ }
+ };
+ ```
+
+### Receive notification when a user's metadata is updated
+
+ To get notified of changes in a user's metadata,, handle the `onUserMetadataUpdated` callback:
+
+ ```javascript
+ const handleMetadataEvents = async function () {
+ signalingManager.signalingEngine.on("UserMetaDataUpdated", function (uid, rtmMetadata) {
+ if (typeof rtmMetadata !== "undefined") {
+ const eventArgs = { uid: uid, rtmMetadata: rtmMetadata }
+ eventsCallback("UserMetaDataUpdated", eventArgs)
+ }
+ });
+ }
+ ```
+
+### Manage Locks
+
+ Locks enable you to safely share critical resources between different users and processes in a distributed system. To manage locks, call `setLock`, `acquireLock`, `releaseLock`, `removeLock`, `revokeLock`, and `getLock`.
+
+ ```javascript
+ const setLock = async function (channelName, channelType, lockName, ttl) {
+ // Create a new lock
+ try{
+ const result = await signalingManager
+ .getSignalingEngine().lock.setLock(
+ channelName, channelType, lockName, { ttl: ttl }
+ );
+ } catch (status) {
+ messageCallback(status.reason);
+ }
+ }
+
+ const acquireLock = async function (channelName, channelType, lockName, retry) {
+ // Acquire exclusive use of the named lock
+ try{
+ const result = await signalingManager
+ .getSignalingEngine().lock.acquireLock(
+ channelName, channelType, lockName, {retry: retry}
+ );
+ } catch (status) {
+ messageCallback(status.reason);
+ }
+ }
+
+ const releaseLock = async function (channelName, channelType, lockName) {
+ // Release a lock after use to make it available for others
+ try{
+ const result = await signalingManager
+ .getSignalingEngine().lock.releaseLock(
+ channelName, channelType, lockName,
+ );
+ } catch (status) {
+ messageCallback(status.reason);
+ }
+ }
+
+ const removeLock = async function (channelName, channelType, lockName) {
+ // Delete a lock
+ try{
+ const result = await signalingManager
+ .getSignalingEngine().lock.removeLock(
+ channelName, channelType, lockName,
+ );
+ } catch (status) {
+ messageCallback(status.reason);
+ }
+ }
+
+ const getLock = async function (channelName, channelType) {
+ // Get details of all current locks in the channel
+ try{
+ const result = await signalingManager
+ .getSignalingEngine().lock.getLock(
+ channelName, channelType
+ );
+ messageCallback(`getLock succeeded. Total ${result.totalLocks } locks: ${JSON.stringify(result.lockDetails)}`)
+ } catch (status) {
+ messageCallback(status.reason);
+ }
+ }
+ ```
+
\ No newline at end of file
diff --git a/shared/signaling/storage/project-test/android.mdx b/shared/signaling/storage/project-test/android.mdx
new file mode 100644
index 000000000..02c772144
--- /dev/null
+++ b/shared/signaling/storage/project-test/android.mdx
@@ -0,0 +1,23 @@
+
+
+3. In Android Studio, open `app/java/com.example./MainActivity`, and update `appId`, `channelName` and `token` with the values for your temporary token.
+
+4. Connect a physical Android device to your development device.
+
+5. In Android Studio, click **Run app**. A moment later you see the project installed on your device.
+
+ If this is the first time you run the project, grant microphone and camera access to your app.
+
+
+6. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+6. Click **Join** to start a call.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
diff --git a/shared/signaling/storage/project-test/electron.mdx b/shared/signaling/storage/project-test/electron.mdx
new file mode 100644
index 000000000..5ba3fe668
--- /dev/null
+++ b/shared/signaling/storage/project-test/electron.mdx
@@ -0,0 +1,25 @@
+
+
+3. In _preload.js_, update `appID`, `channel` and `token` with your values.
+
+4. Run the app
+
+ Execute the following command in the terminal:
+
+ ```bash
+ npm start
+ ```
+ You see your app opens a window named **Get started with **.
+
+
+5. To join as a host, select **Host** and click **Join**.
+
+
+
+
+5. To connect to a channel, click **Join**.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
diff --git a/shared/signaling/storage/project-test/flutter.mdx b/shared/signaling/storage/project-test/flutter.mdx
new file mode 100644
index 000000000..62daa8bba
--- /dev/null
+++ b/shared/signaling/storage/project-test/flutter.mdx
@@ -0,0 +1,23 @@
+
+
+3. In your IDE, open `main.dart`, and update `appId`, `channelName` and `token` with the values for your temporary token.
+
+4. Connect a test device to your development device.
+
+5. In your IDE, click *Run app* or execute `flutter run lib/main.dart`. A moment later you see the project installed on your device.
+
+ If this is the first time you run the project, grant microphone and camera access to your .
+
+
+6. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local stream is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+6. Click **Join** to start a call.
+
+
+ You see your connects to the from a restricted network environment using the cloud proxy service.
+
+
diff --git a/shared/signaling/storage/project-test/index.mdx b/shared/signaling/storage/project-test/index.mdx
new file mode 100644
index 000000000..1a9c70aad
--- /dev/null
+++ b/shared/signaling/storage/project-test/index.mdx
@@ -0,0 +1,25 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import ReactNative from './react-native.mdx';
+import Electron from './electron.mdx';
+import Flutter from './flutter.mdx';
+import MacOS from './macos.mdx'
+import Unity from './unity.mdx'
+import Windows from './windows.mdx';
+
+import Test from '@docs/shared/common/project-test/index.mdx';
+
+To test this functionality:
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/storage/project-test/ios.mdx b/shared/signaling/storage/project-test/ios.mdx
new file mode 100644
index 000000000..f3011d0fa
--- /dev/null
+++ b/shared/signaling/storage/project-test/ios.mdx
@@ -0,0 +1,24 @@
+
+
+3. In the `ViewController`, update `appID`, `channelName`, and `token` with the values from .
+
+4. Run your using either a physical or a simulator iOS device.
+
+ If this is the first time you run the project, grant microphone and camera access to your .
+
+ If you use an iOS simulator, you see the remote video only. You cannot see the local video stream because of Apple simulator hardware restrictions.
+
+
+5. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+
+You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
+
diff --git a/shared/signaling/storage/project-test/macos.mdx b/shared/signaling/storage/project-test/macos.mdx
new file mode 100644
index 000000000..268fbc910
--- /dev/null
+++ b/shared/signaling/storage/project-test/macos.mdx
@@ -0,0 +1,20 @@
+
+
+3. In the `ViewController`, update `appID`, `channelName`, and `token` with the values from .
+
+4. Run your .
+
+
+5. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+
+You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
+
diff --git a/shared/signaling/storage/project-test/react-native.mdx b/shared/signaling/storage/project-test/react-native.mdx
new file mode 100644
index 000000000..ba94866cc
--- /dev/null
+++ b/shared/signaling/storage/project-test/react-native.mdx
@@ -0,0 +1,38 @@
+
+3. In `App.tsx`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+1. Run your app:
+
+ - *Android*
+
+ 1. Enable Developer options on your Android device, and then connect it to your computer using a USB cable.
+ 1. Run `npx react-native run-android` in the project root directory.
+
+ - *iOS:*
+
+ 1. Open the `ProjectName/ios/ProjectName.xcworkspace` folder with Xcode.
+ 1. Connect your iOS device to your Mac using a USB cable.
+ 1. Click the **Build and run** button in Xcode.
+
+
+ If this is the first time you run the project, you need to grant microphone and camera access to your .
+
+
+5. Select an option and click **Join** to start a session.
+ - When you join as a **Host**, the local video is published and played in the .
+ - When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+Now, you can see yourself on the device screen and talk to the remote user using your .
+
+
+
+5. Click **Join** to start a call.
+
+Now, you can talk to the remote user using your .
+
+
+
diff --git a/shared/signaling/storage/project-test/swift.mdx b/shared/signaling/storage/project-test/swift.mdx
new file mode 100644
index 000000000..5b918afae
--- /dev/null
+++ b/shared/signaling/storage/project-test/swift.mdx
@@ -0,0 +1,24 @@
+
+1. Create an instance for the first user:
+
+ 1. In `ViewController`, update the value of `token` with a valid RTM token for user ID `userA`. To obtain an RTM token, see [Secure authentication with tokens](./authentication-workflow).
+
+ 1. Connect a physical device to your development device.
+
+ 1. In Xcode, click **Run app**. A moment later you see the installed on your device.
+
+1. Create an instance for the second user:
+
+ 1. In `ViewController`, update the value of `token` with a valid RTM token for user ID `userB`.
+
+ 1. Run the modified app on a device emulator or a second physical device.
+
+1. On each device, enter the user ID corresponding to the RTM token, and tap **Login** to connect to .
+
+1. On each device, enter the channel name `demo` and tap **Join** to connect to the same channel. You see both users in the user list on each device.
+
+1. In the user list on either device, select the user ID of a remote user. You see a toast message showing metadata for that user.
+
+1. On either device, tap the button to change the status of the local user from **Available** to **Busy**. You see that the status icon for the user is updated on the remote device.
+
+1. Tap **Leave** to exit the channel and then **Logout** of .
diff --git a/shared/signaling/storage/project-test/unity.mdx b/shared/signaling/storage/project-test/unity.mdx
new file mode 100644
index 000000000..1557aa689
--- /dev/null
+++ b/shared/signaling/storage/project-test/unity.mdx
@@ -0,0 +1,17 @@
+
+
+3. In your script file, update `_appID`, `_channelName` and `_token` with the values for your temporary token.
+
+4. In **Unity Editor**, click **Play**. A moment later you see the running on your development device.
+
+
+5. To join as a host, select **Host** and click **Join**.
+
+
+
+5. To connect to a channel, click **Join**.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
\ No newline at end of file
diff --git a/shared/signaling/storage/project-test/web.mdx b/shared/signaling/storage/project-test/web.mdx
new file mode 100644
index 000000000..b3f1de736
--- /dev/null
+++ b/shared/signaling/storage/project-test/web.mdx
@@ -0,0 +1,52 @@
+
+
+4. **Login to as a first user**
+
+ 1. Enter a numeric **User ID** and press **Login** to log in to .
+
+ 1. Enter a **Channel name** and press **Subscribe** to subscribe to a channel. You see the following:
+
+ 1. Your User ID appears in the **Users in the channel** list.
+ 1. Under **Channel Metadata**, you see all the key-value pairs previously saved to the channel's storage.
+ 1. In the log events section at the bottom, you see notifications confirming update of user metadata.
+
+4. **Login to as another user**
+
+ 1. In another browser tab, log in using a different **User ID**. Enter the same channel name as before, and press **Subscribe**.
+
+ You see that both User IDs appear in the user lists in both tabs.
+
+1. **Test metadata**
+
+ In either tab, type in a **Key** and the **Value** you wish to store in the channel metadata, then press
+ **Update**. You see that the key-value pair is added or updated under **Channel Metadata** in both tabs.
+
+1. **Test version control**
+
+ 1. Under **Set channel metadata**, type a new **Value** for an existing channel metadata **Key**. Enter a random positive integer for **Revision** and press **Update**. The log events section shows that the storage operation fails due to `outdated revision`.
+
+ 1. Update the **Revision** value to show the same number as displayed against the metadata key under **Channel Metadata**, then press **Update** again. This time, the operation succeeds.
+
+1. **Test locks**
+
+ 1. Under **Manage Locks**, type a **Lock Name** and press **Set**. You see a `SET` event notification confirming creation of the lock.
+
+ 1. In **Lock to apply**, enter the same lock name and press **Update**. You see an error reporting that the lock has not been acquired.
+
+ 1. Press **Acquire**. You see an `ACQUIRED` event notification confirming acquisition of the lock.
+
+ 1. Press **Update**, this time the channel key value is updated under **Channel Metadata**.
+
+ 1. Press **Get**. You see details of all current locks in the logs section.
+
+ 1. Press **Release** to release the lock, and **Remove** to delete it.
+
+1. **Test metadata**
+
+ 1. In **Save bio to user metadata**, type in some text and press **Update Bio**. You see user metadata `UPDATE`
+ notifications in both tabs.
+
+ 1. In the other browser tab, under **Users in the channel** select the user for which you modified the User bio.
+ You see their updated metadata displayed in the log events section.
+
+
diff --git a/shared/signaling/storage/project-test/windows.mdx b/shared/signaling/storage/project-test/windows.mdx
new file mode 100644
index 000000000..22eade59e
--- /dev/null
+++ b/shared/signaling/storage/project-test/windows.mdx
@@ -0,0 +1,20 @@
+
+ 3. In `CAgoraImplementationDlg.h`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+ 4. In Visual Studio, click **Local Windows Debugger**. A moment later you see the project running on your development device.
+
+ If this is the first time you run the project, you need to grant microphone and camera access to your .
+
+
+ 5. Select an option and click **Join** to start a session.
+ - When you join as a **Host**, the local video is published and played in the .
+ - When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+ 5. Click **Join** to start a call. Now, you can see yourself on the device screen and talk to the remote user using your .
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
\ No newline at end of file
diff --git a/shared/signaling/storage/reference/android.mdx b/shared/signaling/storage/reference/android.mdx
new file mode 100644
index 000000000..3d501ade8
--- /dev/null
+++ b/shared/signaling/storage/reference/android.mdx
@@ -0,0 +1,755 @@
+
+
+### Metadata API
+
+Use the following metadata API to implement your solution:
+
+
+#### User metadata
+
+Creates an `RtmMetadataItem` instance.
+
+**Method**
+
+```javascript
+ public class RtmMetadataItem
+```
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("mode", "emo");
+```
+
+**Response**
+
+Returns a `RtmMetadataItem` instance. `RtmMetadataItem` is a basic unit item of a `UserMetadata` and
+ `ChannelMetadata`. It contains the following properties:
+
+| Property Name | Type | Description |
+| --------------------------------- | -------- | ------------------------------------------------------------------- |
+| `void setKey(String key)` | function | Set key for current `RtmMetadataItem` |
+| `String getKey()` | function | Get key for current `RtmMetadataItem` |
+| `void setValue(String value)` | function | Set value for current `RtmMetadataItem` |
+| `String getValue()` | function | Get value for current `RtmMetadataItem` |
+| `void setRevision(long revision)` | function | Set revision for current `RtmMetadataItem` |
+| `long getRevision()` | function | Get revision for current `RtmMetadataItem` |
+| `long getLastUpdateTs()` | function | Get updated time for current `RtmMetadataItem` |
+| `String getAuthorUserId()` | function | Get the uid of who update this record for current `RtmMetadataItem` |
+
+##### Get user metadata
+
+
+
+Gets all metadataItems of a specified user.
+
+**Method**
+
+```java
+void getUserMetadata(String userId, ResultCallback resultCallback)
+```
+
+| Parameter | Type | Required | Default | Description |
+|:----------------:|:--------:|:--------:|:-------:|:-----------------------:|
+| `userId` | String | yes | | Unique user identifier. |
+| `resultCallback` | function | yes | | callback function |
+
+**Basic Use**
+
+```java
+mRtmClient.getUserMetadata("Tony", new ResultCallback(){
+ @Override
+ public void onSuccess(RtmMetadata userMetadata){
+ // process success result!
+ }
+ @Override
+ public void onFailure(int errorInfo){
+ // process failure result!
+ }
+});
+```
+
+**Response**
+
+Returns a type `RtmMetadata` for the specific `uid` which contains the following properties:
+
+| Property Name | Type | Description |
+|:---------------:|:------------------------:|:------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | `List` | `RtmMetadataItem` type array which contains a single Key-Value metadata item for user, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `majorRevision` | number | The major revision for this user metadata |
+
+
+
+
+
+
+##### Set user metadata
+
+
+
+Set the local user’s metadata.
+
+**Method**
+
+```java
+void setLocalUserMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:------------------------:|:------------------------------------------------------------------------------------------------------------------------------ |
+| `items` | `List` | `RtmMetadataItem` type list which contains a single Key-Value metadata item for user, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+
+`RtmMetadataOptions` is a set of optional properties for operations, it contains the following properties:
+
+| Property Name | Type | Description |
+| -------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `majorRevision` | number | Revison control parametes. when the `majorRevision` you supplied is as same as the one in the storage, this operation will success. |
+| `enableRecordTs` | bool | When it is set to `true`, the final `RtmMetadataItem` will record the updating time automatically |
+| `enableRecordUserId` | bool | When it is set to `true`, the final `RtmMetadataItem` will record who have updated this item automatically |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("mode", "emo");
+RtmMetadataItem item2 = new RtmMetadataItem("gender", "male");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmClient.setLocalUserMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "setLocalUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "setLocalUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> caution: This operation will reset all current meatadata and set a new one.
+
+##### Add user metadata
+
+
+
+Adds metadata items to local user’s metadata.
+
+**Method**
+
+```javascript
+void addLocalUserMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback);
+```
+
+| Property Name | Type | Description |
+|:----------------:|:-------------------------:|:------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | List\< `RtmMetadataItem` > | `RtmMetadataItem` type array which contains a single Key-Value metadata item for user, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("mode", "emo");
+RtmMetadataItem item2 = new RtmMetadataItem("gender", "male");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmClient.addLocalUserMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "addLocalUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "addLocalUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution: This operation will add new metadata items for current user. It will report errors if the key of new `RtmMetadataItem` has already existed in the user metadata.
+
+##### Clear user metadata
+
+
+
+Delete all the local user’s metadata items.
+
+**Method**
+
+```java
+void clearLocalUserMetadata(RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:--------------------:|:--------------------------------------------------------------------------------------------------- |
+| `options` | `RtmMetadataOptions` | add optional propeties for current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmClient.clearLocalUserMetadata(options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "clearLocalUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "clearLocalUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution: This operation will clean all user metadata.
+
+##### Update user metadata
+
+
+
+Update the local user’s metadata items.
+
+**Method**
+
+```java
+void updateLocalUserMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:------------------------:|:------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | `List`| `RtmMetadataItem` type array which contains a single Key-Value metadata item for user, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("mode", "emo");
+RtmMetadataItem item2 = new RtmMetadataItem("gender", "male");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmClient.updateLocalUserMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "updateLocalUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "updateLocalUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution : This operation can only valid for updating the existing metadata items, or it will report errors.
+
+##### Delete user metadata
+
+
+
+Delete the local user’s metadata items.
+
+**Method**
+
+```java
+void deleteLocalUserMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:-------------------------:|:------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | List\< `RtmMetadataItem` > | `RtmMetadataItem` type array which contains a single Key-Value metadata item for user, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current user metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("mode", "");
+RtmMetadataItem item2 = new RtmMetadataItem("gender", "");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmClient.deleteLocalUserMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "deleteLocalUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "deleteLocalUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution:This operation will always excute regardless of the existing of metadata items
+
+##### Subscribe user metadata
+
+
+
+Subscribe to user metadata update events for a specific user.
+
+**Method**
+
+```java
+void subscribeUserMetadata(String userId, ResultCallback resultCallback);
+```
+
+| Parameter | Type | Required | Default | Description |
+|:----------------:|:--------:|:--------:|:-------:|:-----------------------:|
+| `userid` | String | yes | | Unique user identifier. |
+| `resultCallback` | function | yes | | callback function |
+
+**Basic Use**
+
+```java
+mRtmClient.subscribeUserMetadata("Tony", new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "subscribeUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "subscribeUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+##### Unsubscribe user metadata
+
+
+
+Unsubscribe to user metadata update events for a specific user.
+
+**Method**
+
+```java
+void unsubscribeUserMetadata(String userId, ResultCallback resultCallback)
+```
+
+| Parameter | Type | Required | Default | Description |
+|:----------------:|:--------:|:--------:|:-------:|:-----------------------:|
+| `userId` | String | yes | | Unique user identifier. |
+| `resultCallback` | function | yese | | callback function |
+
+**Basic Use**
+
+```java
+mRtmClient.unsubscribeUserMetadata("Tony", new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "unsubscribeUserMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "unsubscribeUserMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+##### User metadata event
+
+
+
+it will occur when user's metadata are updated(`Add`/`Set`/`Clear`/`Update`/`Delete`), You need to complete this procedure yourself, and then you can handle this event when you are subscribing user's metadata.
+
+**Method**
+
+```java
+void onUserMetadataUpdated(String userId, RtmMetadata data)
+```
+
+**Basic Use**
+
+```java
+class RtmClientListener implements RtmClientListener {
+ //..
+ @Override
+ public void onUserMetadataUpdated(String userId, RtmMetadata data) {
+ visualLog_CALLBACK("onUserMetadataUpdated, userId: " + userId);
+ for (RtmMetadataItem item: data.items) {
+ visualLog("Item key: " + item.getKey() + ", value: " + item.getValue()
+ + ", revision: " + item.getRevision() + ", ts: " + item.getLastUpdateTs()
+ + ", uid: " + item.getAuthorUserId());
+ }
+ }
+ //..
+}
+
+rtmClient = RtmClient.createInstance(context, appId, new RtmClientListener());
+```
+
+**Response**
+
+When this event occurs, you can recieve a uid which indicating whose metadata have changed and a type `RtmMetadata` for the user which contains the following properties:
+
+| Property Name | Type | Description |
+|:---------------:|:--------------------------:| -------------------------------------------------------------------------------- |
+| `items` | Array( `RtmMetadataItem` ) | `RtmMetadataItem` type array which contains a single Key-Value metadata item for user |
+| `majorRevision` | number | The major revision for this user metadata |
+
+> **Caution**: It should be noted that the returned data contains the full amount of data of the current user, `Add`/`Set`/`Clear`/`Update`/`Delete` operation all will trigger this event, and you cannot distinguish which operation caused the current event. Need more features, you can use our new version 2.1.
+
+#### Channel metadata
+
+##### Set channel metadata
+
+set the metadata of the channel.
+
+**Method**
+
+```java
+void setChannelMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:-----------------------:|:---------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | List\<`RtmMetadataItem`> | `RtmMetadataItem` type array which contains a single Key-Value metadata item for channel, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+The `RtmMetadataOptions` is a set of optional properties for operations, it contains the following properties:
+
+| Property Name | Type | Description |
+| -------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `majorRevision` | number | Revison control parametes. when the `majorRevision` you supplied is as same as the one in the storage, this operation will success. |
+| `enableRecordTs` | bool | When it is set to `true`, the final `RtmMetadataItem` will record the updating time automatically |
+| `enableRecordUserId` | bool | When it is set to `true`, the final `RtmMetadataItem` will record who have updated this item automatically |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("Announcement", "Welcome to RTM");
+RtmMetadataItem item2 = new RtmMetadataItem("Channel_type", "Public");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmChannel.setChannelMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "setChannelMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "setChannelMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> caution: This operation will `reset` all current meatadata of channel and set a new one for channel.
+
+##### Add channel metadata
+
+
+
+Add new metadata items to the channel.
+
+**Method**
+
+```java
+void addChannelMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:-------------------------:|:---------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | List\<(`RtmMetadataItem` > | `RtmMetadataItem` type array which contains a single Key-Value metadata item for channel, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("Announcement", "Welcome to RTM");
+RtmMetadataItem item2 = new RtmMetadataItem("Channel_type", "Public");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmChannel.addChannelMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "addChannelMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "addChannelMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution: This operation will add new metadata items for current channel. It will report errors if the key of new `RtmMetadataItem` has already existed in the channel metadata.
+
+##### Clear channel metadata
+
+
+
+delete all metadata items of the channel.
+
+**Method**
+
+```java
+clearChannelMetadata(RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:--------------------:|:------------------------------------------------------------------------------------------------------ |
+| `options` | `RtmMetadataOptions` | add optional propeties for current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmChannel.clearChannelMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "clearChannelMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "clearChannelMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution: This operation will clean all metadata of the specific channel.
+
+##### Update channel metadata
+
+
+
+Update metadata items of the channel.
+
+**Method**
+
+```java
+updateChannelMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:------------------------:|:---------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | `List`| `RtmMetadataItem` type array which contains a single Key-Value metadata item for channel, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("Announcement", "Welcome to RTM");
+RtmMetadataItem item2 = new RtmMetadataItem("Channel_type", "Public");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmChannel.updateChannelMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "updateChannelMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "updateChannelMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution : This operation can only valid for updating the existing metadata items, or it will report errors.
+
+##### Delete channel metadata
+
+
+
+delete metadata items of the channel.
+
+**Method**
+
+```java
+void deleteChannelMetadata(List items, RtmMetadataOptions options, ResultCallback resultCallback)
+```
+
+| Property Name | Type | Description |
+|:----------------:|:-----------------------:|:---------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | `List` | `RtmMetadataItem` type array which contains a single Key-Value metadata item for channel, see [`RtmMetadataItem`](#RtmMetadataItem) for more information |
+| `options` | `RtmMetadataOptions` | add optional propeties for current channel metadata, see [`RtmMetadataOptions`](#RtmMetadataOptions) for more information |
+| `resultCallback` | function | callback function |
+
+**Basic Use**
+
+```java
+RtmMetadataItem item1 = new RtmMetadataItem("Announcement", "");
+RtmMetadataItem item2 = new RtmMetadataItem("Channel_type", "");
+ArrayList metadata = new ArrayList();
+metadata.add(item1);
+metadata.add(item2);
+RtmMetadataOptions options = new RtmMetadataOptions(-1, ture, false);
+mRtmChannel.deleteChannelMetadata(metadata, options, new ResultCallback() {
+ @Override
+ public void onSuccess(Void unused) {
+ visualLog_CALLBACK( "deleteChannelMetadata onSuccess");
+ }
+
+ @Override
+ public void onFailure(ErrorInfo errorInfo) {
+ visualLog_CALLBACK( "deleteChannelMetadata onFailure, error: " + errorInfo.toString());
+ }
+});
+```
+
+**Response**
+
+None
+
+> Caution:This operation will always excute regardless of the existing of metadata items
+
+##### Get channel metadata
+
+
+
+get all metadata items of the channel.
+
+**Method**
+
+```javascript
+getChannelMetadata(ResultCallback resultCallback)
+```
+
+| Parameter | Type | Required | Default | Description |
+|:----------------:|:--------:|:--------:|:-------:|:-----------------:|
+| `resultCallback` | function | yes | | callback function |
+
+**Basic Use**
+
+```java
+mRtmChannel.getChannelMetadata( new ResultCallback(){
+ @Override
+ public void onSuccess(RtmMetadata channelMetadata){
+ // process success result!
+ }
+ @Override
+ public void onFailure(int errorInfo){
+ // process failure result!
+ }
+});
+```
+
+**Response**
+
+Returns a type `RtmMetadata` for the specific `channel` which contains the following properties:
+
+| Property Name | Type | Description |
+| --------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | List< `RtmMetadataItem` > | `RtmMetadataItem` type array which contains a single Key-Value metadatafor channel, see `[RtmMetadataIte](1)` for more information |
+| `majorRevision` | number | The major revision for this channel metadata |
+
+> **Caution**: If the specific `channel` have not set any user metadata yet, this operation will return a empty {}.
+
+##### Listen channel metadata update event
+
+When you join channel ,you will automatically Listen the Channel metadata Update Event
+
+##### Channel metadata event
+
+This occurs when channel's metadata are updated, You need to complete this procedure yourself, and then you can handle
+ this event when you are in the channel
+
+**Method**
+
+```javascript
+void onMetadataUpdated(RtmMetadata data)
+```
+
+**Basic Use**
+
+```java
+private RtmChannelListener rtmChannelListener = new RtmChannelListener(){
+
+ @Override
+ public void onMetadataUpdated(RtmMetadata data) {
+ visualLog_CALLBACK("onMetadataUpdated");
+ for (RtmMetadataItem item: data.items) {
+ visualLog( "Item key: " + item.getKey() + ", value: " + item.getValue()
+ + ", revision: " + item.getRevision() + ", ts: " + item.getLastUpdateTs()
+ + ", userId: " + item.getAuthorUserId());
+ }
+ }
+};
+
+RtmChannel rtmChannel = mRtmClient.createChannel(channelId.toString(), rtmChannelListener);
+```
+
+**Response**
+
+When this event occurs, you can recieve a type `RtmMetadata` for the specific `channel` which contains the following properties:
+
+| Property Name | Type | Description |
+|:---------------:|:-------------------------:| ----------------------------------------------------------------------------------- |
+| `items` | List\< `RtmMetadataItem` > | `RtmMetadataItem` type array which contains a single Key-Value metadata itemfor channel |
+| `majorRevision` | number | The major revision for this channel metadata |
+
+> **Caution**: It should be noted that the returned data contains the full amount of data of the current channel, `Add`/`Set`/`Clear`/`Update`/`Delete` operation all will trigger this event, and you cannot distinguish which operation caused the current event. Need more features, you can use our new version 2.1.
+
+
diff --git a/shared/signaling/storage/reference/index.mdx b/shared/signaling/storage/reference/index.mdx
new file mode 100644
index 000000000..7c7e1db85
--- /dev/null
+++ b/shared/signaling/storage/reference/index.mdx
@@ -0,0 +1,9 @@
+import Android from './android.mdx';
+import Web from './web.mdx';
+import Ios from './oc.mdx'
+
+
+
+
+
+
diff --git a/shared/signaling/storage/reference/ios.mdx b/shared/signaling/storage/reference/ios.mdx
new file mode 100644
index 000000000..177223d72
--- /dev/null
+++ b/shared/signaling/storage/reference/ios.mdx
@@ -0,0 +1,6 @@
+import Oc from './oc.mdx';
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/storage/reference/oc.mdx b/shared/signaling/storage/reference/oc.mdx
new file mode 100644
index 000000000..999a3dde3
--- /dev/null
+++ b/shared/signaling/storage/reference/oc.mdx
@@ -0,0 +1,926 @@
+
+
+
+
+### Metadata API
+
+Use the following metadata API to implement your solution:
+
+
+#### User Metadata
+
+Create Metadata Item
+
+**Description**
+
+Creates an `AgoraRtmMetadataItem` instance.
+
+**Class**
+
+```objective-c
+ @interface AgoraRtmMetadataItem : NSObject
+```
+
+**Bassic Use**
+
+```objective-c
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"mode";
+item1.value = @"emo";
+```
+
+**Response**
+
+Returns a `AgoraRtmMetadataItem` instance, The `AgoraRtmMetadataItem` is a basic unit item of a `UserMetadata` and
+`ChannelMetadata`, it contains the following properties:
+
+| Property Name | Type | Description |
+| ------------- | --------- | ------------------------------------------------------------------------ |
+| key | NSString | Set/Get key for current `AgoraRtmMetadataItem` |
+| value | NSString | Set/Get value for current `AgoraRtmMetadataItem` |
+| revision | NSInteger | Set/Get revision for current `AgoraRtmMetadataItem` |
+| updateTs | NSInteger | Get updated time for current `AgoraRtmMetadataItem` |
+| authorUserId | NSString | Get the uid of who update this record for current `AgoraRtmMetadataItem` |
+
+##### Get User Metadata
+
+**Description**
+
+Gets all metadataItems of a specified user.
+
+**Method**
+
+```objective-c
+- (void)getUserMetadataWithCompletion:(NSString* _Nonnull)userId
+ completion:(AgoraRtmGetUserMetadataBlock _Nullable)completionBlock;
+```
+
+| Parameter | Type | Required | Default | Description |
+|:-----------------:|:--------:|:--------:|:-------:|:-----------------------:|
+| `userId` | NSString | yes | | Unique user identifier. |
+| `completionBlock` | function | yes | | callback function |
+
+**Bassic Use**
+
+```objective-c
+mRtmKit getUserMetadataWithCompletion:@"Tony" completion:^(NSString * _Nonnull userId, AgoraRtmMetadata * _Nullable data, AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ if (data.items.count > 0) {
+ for (AgoraRtmMetadataItem *item in data.items) {
+ // process the metadataItems
+ }
+ } else {
+ //No metadata found for user
+ }
+ } else {
+ // handle the errorInfo
+ }
+});
+```
+
+**Response**
+
+The `AgoraRtmMetadata` represents the metadata associated with a user and keeps an array of metadata items:
+
+| Parameter | Type | Required | Description |
+| ------------- | -------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
+| items | NSArray< AgoraRtmMetadataItem *> | yes | AgoraRtmMetadataItem` type array which contains a single Key-Value metadate for user,see [AgoraRtmMetadataItem](#RtmMetadataItem) for more information |
+| majorRevision | NSInteger | yes | Get the major revision for this user metadata. |
+
+##### Set User Metadata
+
+**Description**
+
+Set the local user’s metadata.
+
+**Method**
+
+```objective-c
+- (void)setLocalUserMetadataWithCompletion:(NSArray< AgoraRtmMetadataItem *> * _Nonnull)items
+ metadataOptions:(AgoraRtmMetadataOptions* _Nullable)options
+ completion:(AgoraRtmMetadataModifyBlock _Nullable)completionBlock;
+```
+
+| Property Name | Type | Description |
+|:-----------------:|:--------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | NSArray\< AgoraRtmMetadataItem *> | `AgoraRtmMetadataItem` type list which contains a single Key-Value metadate for user,see [AgoraRtmMetadataItem](#RtmMetadataItem) for more information |
+| `options` | `AgoraRtmMetadataOptions` | add optional propeties for current user Metadata, see [AgoraRtmMetadataOptions](#RtmMetadataOptions) for more information |
+| `completionBlock` | function | callback function |
+
+The `AgoraRtmMetadataOptions` is a set of optional properties for operations, it contains the following properties:
+
+| Property Name | Type | Description |
+| -------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `majorRevision` | NSInteger | Revison control parametes. when the `majorRevision` you supplied is as same as the one in the storage, this operation will success. |
+| `enableRecordTs` | BOOL | When it is set to `true`, the final `AgoraRtmMetadataItem` will record the updating time automatically |
+| `enableRecordUserId` | BOOL | When it is set to `true`, the final `AgoraRtmMetadataItem` will record who have updated this item automatically |
+
+**Bassic Use**
+
+```objective-c
+// Create three AgoraRtmMetadataItem instance
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"nickname";
+item1.value = @"Tony Stark";
+
+AgoraRtmMetadataItem *item2 = [[AgoraRtmMetadataItem alloc] init];
+item2.key = @"gender";
+item2.value = @"male";
+
+AgoraRtmMetadataItem *item3 = [[AgoraRtmMetadataItem alloc] init];
+item3.key = @"age";
+item3.value = @"35";
+
+// Add three instances to list
+NSArray *items = @[item1, item2, item3];
+
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke setLocalUserMetadataWithCompletion to set local users's metadata
+[mRtmKit setLocalUserMetadataWithCompletion:items metadataOptions:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The set operation is successful
+ } else {
+ //The set operation is failed;
+ }
+}];
+```
+
+**Response**
+
+None
+
+> caution: This operation will reset all current metadata and set a new one.
+
+##### Add User Metadata
+
+**Description**
+
+Adds metadata items to local user’s metadata.
+
+**Method**
+
+```objective-c
+- (void)addLocalUserMetadataWithCompletion:(NSArray< AgoraRtmMetadataItem *> * _Nonnull)items
+ metadataOptions:(AgoraRtmMetadataOptions* )options
+ completion:(AgoraRtmMetadataModifyBlock _Nullable)completionBlock;
+```
+
+| Property Name | Type | Description |
+|:-----------------:|:--------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | NSArray< AgoraRtmMetadataItem *> | `AgoraRtmMetadataItem` type array which contains a single Key-Value metadate for user,see [AgoraRtmMetadataItem](#RtmMetadataItem) for more information |
+| `options` | `AgoraRtmMetadataOptions` | add optional propeties for current user Metadata, see [AgoraRtmMetadataOptions](#RtmMetadataOptions) for more information |
+| `completionBlock` | function | callback function |
+
+**Bassic Use**
+
+```objective-c
+// Create three AgoraRtmMetadataItem instance
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"nickname";
+item1.value = @"Tony Stark";
+
+AgoraRtmMetadataItem *item2 = [[AgoraRtmMetadataItem alloc] init];
+item2.key = @"gender";
+item2.value = @"male";
+
+AgoraRtmMetadataItem *item3 = [[AgoraRtmMetadataItem alloc] init];
+item3.key = @"age";
+item3.value = @"35";
+
+// Add three instances to list
+NSArray *items = @[item1, item2, item3];
+
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke addLocalUserMetadataWithCompletion to add local users's metadata
+[mRtmKit addLocalUserMetadataWithCompletion:items metadataOptions:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The add operation is successful
+ } else {
+ //The add operation is failed;
+ }
+}];
+```
+
+**Response**
+
+None
+
+> Caution: This operation will add new metadata items for current user. It will report errors if the key of new `AgoraRtmMetadataItem` has already existed in the user metadata.
+
+##### Clear User Metadata
+
+**Description**
+
+Delete all the local user’s metadata items.
+
+Method
+
+```objective-c
+- (void)clearLocalUserMetadataWithCompletion:(AgoraRtmMetadataOptions* _Nullable)options
+ completion:(AgoraRtmMetadataModifyBlock _Nullable)completionBlock;
+```
+
+| Property Name | Type | Description |
+|:-----------------:|:-------------------------:|:------------------------------------------------------------------------------------------------------ |
+| `options` | `AgoraRtmMetadataOptions` | add optional propeties for current user Metadata, see [AgoraRtmMetadataOptions](#RtmMetadataOptions) for more information |
+| `completionBlock` | function | callback function |
+
+**Bassic Use**
+
+```objective-c
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke clearLocalUserMetadataWithCompletion to clear local users's metadata
+[mRtmKit clearLocalUserMetadataWithCompletion:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The clear operation is successful
+ } else {
+ //The clear operation is failed;
+ }
+}];
+```
+
+**Response**
+
+None
+
+> Caution: This operation will clean all user metadata.
+
+##### Update User Metadata
+
+**Description**
+
+Update the local user’s metadata items.
+
+Method
+
+```objective-c
+- (void)updateLocalUserMetadataWithCompletion:(NSArray< AgoraRtmMetadataItem *> * _Nonnull)items
+ metadataOptions:(AgoraRtmMetadataOptions* _Nullable)options
+ completion:(AgoraRtmMetadataModifyBlock _Nullable)completionBlock;
+```
+
+| Property Name | Type | Description |
+|:-----------------:|:--------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | NSArray< AgoraRtmMetadataItem *> | `AgoraRtmMetadataItem` type array which contains a single Key-Value metadate for user,see [AgoraRtmMetadataItem](#RtmMetadataItem) for more information |
+| `options` | `AgoraRtmMetadataOptions` | add optional propeties for current user Metadata, see [AgoraRtmMetadataOptions](#RtmMetadataOptions) for more information |
+| `completionBlock` | function | callback function |
+
+**Bassic Use**
+
+```objective-c
+// Create three AgoraRtmMetadataItem instance
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"nickname";
+item1.value = @"Tony Stark";
+
+AgoraRtmMetadataItem *item2 = [[AgoraRtmMetadataItem alloc] init];
+item2.key = @"gender";
+item2.value = @"male";
+
+AgoraRtmMetadataItem *item3 = [[AgoraRtmMetadataItem alloc] init];
+item3.key = @"age";
+item3.value = @"35";
+
+// Add three instances to list
+NSArray *items = @[item1, item2, item3];
+
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke updateLocalUserMetadataWithCompletion to update local users's metadata
+[mRtmKit updateLocalUserMetadataWithCompletion:items metadataOptions:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The update operation is successful
+ } else {
+ //The update operation is failed;
+ }
+}];
+```
+
+**Response**
+
+None
+
+> Caution : This operation can only valid for updating the existing metadata items, or it will report errors.
+
+##### Delete User Metadata
+
+**Description**
+
+Delete the local user’s metadata items.
+
+**Method**
+
+```objective-c
+- (void)deleteLocalUserMetadataWithCompletion:(NSArray< AgoraRtmMetadataItem *> * _Nonnull)items
+ metadataOptions:(AgoraRtmMetadataOptions* _Nullable)options
+ completion:(AgoraRtmMetadataModifyBlock _Nullable)completionBlock;
+```
+
+| Property Name | Type | Description |
+|:-----------------:|:--------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | NSArray< AgoraRtmMetadataItem *> | `AgoraRtmMetadataItem` type array which contains a single Key-Value metadate for user,see [AgoraRtmMetadataItem](#RtmMetadataItem) for more information |
+| `options` | `AgoraRtmMetadataOptions` | add optional propeties for current user Metadata, see [AgoraRtmMetadataOptions](#RtmMetadataOptions) for more information |
+| `completionBlock` | function | callback function |
+
+**Bassic Use**
+
+```objective-c
+// Create three AgoraRtmMetadataItem instance
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"nickname";
+
+AgoraRtmMetadataItem *item2 = [[AgoraRtmMetadataItem alloc] init];
+item2.key = @"gender";
+
+// Add three instances to list
+NSArray *items = @[item1, item2];
+
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke deleteLocalUserMetadataWithCompletion to delete local users's metadata
+[mRtmKit deleteLocalUserMetadataWithCompletion:items metadataOptions:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The delete operation is successful
+ } else {
+ //The delete operation is failed;
+ }
+}];
+```
+
+**Response**
+
+None
+
+> Caution:This operation will always excute regardless of the existing of metadata items
+
+##### Subscribe User Metadata
+
+**Description**
+
+Subscribe to user metadata update events for a specific users.
+
+**Method**
+
+```objective-c
+- (void)subscribeUserMetadataWithCompletion:(NSString* _Nonnull)userId
+ completion:(AgoraRtmUserMetadataSubscriptionBlock _Nullable)completionBlock;
+```
+
+| Parameter | Type | Required | Default | Description |
+|:-----------------:|:--------:|:--------:|:-------:|:-----------------------:|
+| `userId` | NSString | yes | | Unique user identifier. |
+| `completionBlock` | function | yes | | callback function |
+
+**Bassic Use**
+
+```objective-c
+mRtmKit subscribeUserMetadataWithCompletion:@"Tony" completion:^(NSString * _Nonnull userId, AgoraRtmMetadataSubscriptionErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataSubscriptionErrorOk) {
+ // Subscription is successful
+ } else {
+ // Subscription is failed
+ }
+});
+```
+
+**Response**
+
+None
+
+##### Unsubscribe User Metadata
+
+**Description**
+
+Unsubscribe to user metadata update events for a specific users.
+
+**Method**
+
+```objective-c
+- (void)unsubscribeUserMetadataWithCompletion:(NSString* _Nonnull)userId
+ completion:(AgoraRtmUserMetadataSubscriptionBlock _Nullable)completionBlock;
+```
+
+| Parameter | Type | Required | Default | Description |
+|:-----------------:|:--------:|:--------:|:-------:|:-----------------------:|
+| `userId` | NSString | yes | | Unique user identifier. |
+| `completionBlock` | function | yese | | callback function |
+
+**Bassic Use**
+
+```java
+mRtmKit unsubscribeUserMetadataWithCompletion:@"Tony" completion:^(NSString * _Nonnull userId, AgoraRtmMetadataSubscriptionErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataSubscriptionErrorOk) {
+ // Unsubscription is successful
+ } else {
+ // Unsubscription is failed
+ }
+});
+```
+
+**Response**
+
+None
+
+##### User Metadata Event
+
+**Description**
+
+it will occur when user's metadata are updated(`Add`/`Set`/`Clear`/`Update`/`Delete`), You need to complete this procedure yourself, and then you can handle this event when you are subscribing user's metadata.
+
+**Method**
+
+```objective-c
+- (void)userMetadataUpdated:(NSString * _Nonnull)userId metadata:(AgoraRtmMetadata * _Nonnull)data;
+```
+
+**Bassic Use**
+
+```objective-c
+// Create AgoraRtmKit delegate:self
+AgoraRtmKit* mRtmKit = [[AgoraRtmKit alloc] initWithAppId:self.appID delegate:self];
+
+// Create three AgoraRtmMetadataItem instance
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"nickname";
+item1.value = @"Tony Stark";
+
+AgoraRtmMetadataItem *item2 = [[AgoraRtmMetadataItem alloc] init];
+item2.key = @"gender";
+item2.value = @"male";
+
+AgoraRtmMetadataItem *item3 = [[AgoraRtmMetadataItem alloc] init];
+item3.key = @"age";
+item3.value = @"35";
+
+// Add three instances to list
+NSArray *items = @[item1, item2, item3];
+
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke setLocalUserMetadataWithCompletion to set local users's metadata
+[mRtmKit setLocalUserMetadataWithCompletion:items metadataOptions:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The set operation is successful
+ } else {
+ //The set operation is failed;
+ }
+}];
+
+// Listen for user metadata updates
+- (void)rtmKit:(AgoraRtmKit *)kit userMetadataUpdated:(NSString *)userId metadata:(AgoraRtmMetadata *)data {
+ //the user's metadata is updated
+ for (AgoraRtmMetadataItem *item in data.items) {
+ // process the metadataItems
+ }
+}
+```
+
+**Response**
+
+When this event occurs, you can recieve a uid which indicating whose metadata have changed and a type `AgoraRtmMetadata` for the user which contains the following properties:
+
+| Property Name | Type | Description |
+|:-------------:|:----------------:| ------------------------------------------------------------------------------- |
+| userId | NSString | Unique user identifier. |
+| `data` | AgoraRtmMetadata | `Metadata associated with a user,see [AgoraRtmMetadata](#RtmMetadataItem) for more information. |
+
+> **Caution**: It should be noted that the returned data contains the full amount of data of the current user, `Add`/`Set`/`Clear`/`Update`/`Delete` operation all will trigger this event, and you cannot distinguish which operation caused the current event. Need more features, you can use our new version 2.1.
+
+#### Channel Metadata
+
+##### Set Channel Metadata
+
+**Description**
+
+set the metadata of the channel.
+
+**Method**
+
+```objective-c
+- (void)setChannelMetadataWithCompletion:(NSArray< AgoraRtmMetadataItem *> * _Nonnull)items
+ metadataOptions:(AgoraRtmMetadataOptions* _Nullable)options
+ completion:(AgoraRtmMetadataModifyBlock _Nullable)completionBlock;
+```
+
+| Property Name | Type | Description |
+|:-----------------:|:--------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------- |
+| `items` | NSArray< AgoraRtmMetadataItem *> | AgoraRtmMetadataItem` type list which contains a single Key-Value metadate for channel,see [AgoraRtmMetadataItem](#RtmMetadataItem) for more information |
+| `options` | `AgoraRtmMetadataOptions` | add optional propeties for current channel Metadata, see [AgoraRtmMetadataOptions](#RtmMetadataOptions) for more information |
+| `completionBlock` | function | callback function |
+
+
+The `AgoraRtmMetadataOptions` is a set of optional properties for operations, it contains the following properties:
+
+| Property Name | Type | Description |
+| -------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `majorRevision` | NSInteger | Revison control parametes. when the `majorRevision` you supplied is as same as the one in the storage, this operation will success. |
+| `enableRecordTs` | BOOL | When it is set to `true`, the final `AgoraRtmMetadataItem` will record the updating time automatically |
+| `enableRecordUserId` | BOOL | When it is set to `true`, the final `AgoraRtmMetadataItem` will record who have updated this item automatically |
+
+**Bassic Use**
+
+```objective-c
+// Create three AgoraRtmMetadataItem instance
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"nickname";
+item1.value = @"Tony Stark";
+
+AgoraRtmMetadataItem *item2 = [[AgoraRtmMetadataItem alloc] init];
+item2.key = @"gender";
+item2.value = @"male";
+
+AgoraRtmMetadataItem *item3 = [[AgoraRtmMetadataItem alloc] init];
+item3.key = @"age";
+item3.value = @"35";
+
+// Add three instances to list
+NSArray *items = @[item1, item2, item3];
+
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke setLocalUserMetadataWithCompletion to set channel's metadata
+[mRtmChannel setChannelMetadataWithCompletion:items metadataOptions:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The set operation is successful
+ } else {
+ //The set operation is failed;
+ }
+}];
+```
+
+**Response**
+
+None
+
+> caution: This operation will `reset` all current meatadata of channel and set a new one for channel.
+
+##### Add Channel Metadata
+
+**Description**
+
+Add new metadata items to the channel.
+
+**Method**
+
+```objective-c
+- (void)addChannelMetadataWithCompletion:(NSArray< AgoraRtmMetadataItem *> * _Nonnull)items
+ metadataOptions:(AgoraRtmMetadataOptions* _Nullable)options
+ completion:(AgoraRtmMetadataModifyBlock _Nullable)completionBlock;
+```
+
+| Property Name | Type | Description |
+|:-----------------:|:--------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------ |
+| `items` | NSArray< AgoraRtmMetadataItem *> | `AgoraRtmMetadataItem` type array which contains a single Key-Value metadate for channel,see [AgoraRtmMetadataItem](#RtmMetadataItem) for more information |
+| `options` | `AgoraRtmMetadataOptions` | add optional propeties for current channel Metadata, see [AgoraRtmMetadataOptions](#RtmMetadataOptions) for more information |
+| `completionBlock` | function | callback function |
+
+**Bassic Use**
+
+```objective-c
+// Create three AgoraRtmMetadataItem instance
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"nickname";
+item1.value = @"Tony Stark";
+
+AgoraRtmMetadataItem *item2 = [[AgoraRtmMetadataItem alloc] init];
+item2.key = @"gender";
+item2.value = @"male";
+
+AgoraRtmMetadataItem *item3 = [[AgoraRtmMetadataItem alloc] init];
+item3.key = @"age";
+item3.value = @"35";
+
+// Add three instances to list
+NSArray *items = @[item1, item2, item3];
+
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke addChannelMetadataWithCompletion to add channel's metadata
+[mRtmChannel addChannelMetadataWithCompletion:items metadataOptions:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The add operation is successful
+ } else {
+ //The add operation is failed;
+ }
+}];
+```
+
+**Response**
+
+None
+
+> Caution: This operation will add new metadata items for current channel. It will report errors if the key of new `AgoraRtmMetadataItem` has already existed in the channel metadata.
+
+##### Clear Channel Metadata
+
+**Description**
+
+delete all metadata items of the channel.
+
+**Method**
+
+```objective-c
+- (void)clearChannelMetadataWithCompletion:(AgoraRtmMetadataOptions* _Nullable)options
+ completion:(AgoraRtmMetadataModifyBlock _Nullable)completionBlock;
+```
+
+| Property Name | Type | Description |
+|:-----------------:|:-------------------------:|:--------------------------------------------------------------------------------------------------------- |
+| `options` | `AgoraRtmMetadataOptions` | add optional propeties for current channel Metadata, see [AgoraRtmMetadataOptions](#RtmMetadataOptions) for more information |
+| `completionBlock` | function | callback function |
+
+**Bassic Use**
+
+```objective-c
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke clearChannelMetadataWithCompletion to clear channel's metadata
+[mRtmChannel clearChannelMetadataWithCompletion:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The clear operation is successful
+ } else {
+ //The clear operation is failed;
+ }
+}];
+```
+
+**Response**
+
+None
+
+> Caution: This operation will clean all metadata of the specific channel.
+
+##### Update Channel Metadata
+
+**Description**
+
+Update metadata items of the channel.
+
+**Method**
+
+```objective-c
+ - (void)updateChannelMetadataWithCompletion:(NSArray< AgoraRtmMetadataItem *> * _Nonnull)items
+ metadataOptions:(AgoraRtmMetadataOptions* _Nullable)options
+ completion:(AgoraRtmMetadataModifyBlock _Nullable)completionBlock;
+```
+
+| Property Name | Type | Description |
+|:-----------------:|:--------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------ |
+| `items` | NSArray< AgoraRtmMetadataItem *> | `AgoraRtmMetadataItem` type array which contains a single Key-Value metadate for channel,see [AgoraRtmMetadataItem](#RtmMetadataItem) for more information |
+| `options` | `AgoraRtmMetadataOptions` | add optional propeties for current channel Metadata, see [AgoraRtmMetadataOptions](#RtmMetadataOptions) for more information |
+| `completionBlock` | function | callback function |
+
+**Bassic Use**
+
+```objective-c
+// Create three AgoraRtmMetadataItem instance
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"nickname";
+item1.value = @"Tony Stark";
+
+AgoraRtmMetadataItem *item2 = [[AgoraRtmMetadataItem alloc] init];
+item2.key = @"gender";
+item2.value = @"male";
+
+AgoraRtmMetadataItem *item3 = [[AgoraRtmMetadataItem alloc] init];
+item3.key = @"age";
+item3.value = @"35";
+
+// Add three instances to list
+NSArray *items = @[item1, item2, item3];
+
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke updateLocalUserMetadataWithCompletion to update channel's metadata
+[mRtmChannel updateChannelMetadataWithCompletion:items metadataOptions:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The update operation is successful
+ } else {
+ //The update operation is failed;
+ }
+}];
+```
+
+**Response**
+
+None
+
+> Caution : This operation can only valid for updating the existing metadata items, or it will report errors.
+
+##### Delete Channel Metadata
+
+**Description**
+
+delete metadata items of the channel.
+
+**Method**
+
+```objective-c
+- (void)deleteChannelMetadataWithCompletion:(NSArray< AgoraRtmMetadataItem *> * _Nonnull)items
+ metadataOptions:(AgoraRtmMetadataOptions* _Nullable)options
+ completion:(AgoraRtmMetadataModifyBlock _Nullable)completionBlock;
+```
+
+| Property Name | Type | Description |
+|:-----------------:|:--------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------ |
+| `items` | NSArray< AgoraRtmMetadataItem *> | `AgoraRtmMetadataItem` type array which contains a single Key-Value metadate for channel,see [AgoraRtmMetadataItem](#RtmMetadataItem) for more information |
+| `options` | `AgoraRtmMetadataOptions` | add optional propeties for current channel Metadata, see [AgoraRtmMetadataOptions](#RtmMetadataOptions) for more information |
+| `completionBlock` | function | callback function |
+
+**Bassic Use**
+
+```objective-c
+// Create three AgoraRtmMetadataItem instance
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"nickname";
+
+AgoraRtmMetadataItem *item2 = [[AgoraRtmMetadataItem alloc] init];
+item2.key = @"gender";
+
+// Add three instances to list
+NSArray *items = @[item1, item2];
+
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke deleteChannelMetadataWithCompletion to delete channel's metadata
+[mRtmChannel deleteChannelMetadataWithCompletion:items metadataOptions:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The delete operation is successful
+ } else {
+ //The delete operation is failed;
+ }
+}];
+```
+
+**Response**
+
+None
+
+> Caution:This operation will always excute regardless of the existing of metadata items
+
+##### Get Channel Metadata
+
+**Description**
+
+get all metadata items of the channel.
+
+**Method**
+
+```obj
+- (void)getChannelMetadataWithCompletion:(AgoraRtmGetChannelMetadataBlock _Nullable)completionBlock;
+```
+
+| Parameter | Type | Required | Default | Description |
+|:-----------------:|:--------:|:--------:|:-------:|:-----------------:|
+| `completionBlock` | function | yes | | callback function |
+
+**Bassic Use**
+
+```objective-c
+mRtmChannel getChannelMetadataWithCompletion:^(AgoraRtmMetadata * _Nullable data, AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ if (data.items.count > 0) {
+ for (AgoraRtmMetadataItem *item in data.items) {
+ // process the metadataItems
+ }
+ } else {
+ //No metadata found for user
+ }
+ } else {
+ // handle the errorInfo
+ }
+});
+```
+
+**Response**
+
+Returns a type `AgoraRtmMetadata` for the specific `channel` which contains the following properties:
+
+| Parameter | Type | Required | Description |
+| ------------- | -------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
+| items | NSArray< AgoraRtmMetadataItem *> | yes | AgoraRtmMetadataItem` type array which contains a single Key-Value metadate for channel,see `[AgoraRtmMetadataItem](#RtmMetadataItem)` for more information |
+| majorRevision | NSInteger | yes | Get the major revision for this channel metadata. |
+
+> **Caution**: If the specific `channel` have not set any user metadata yet, this operation will return a empty {}.
+
+##### Listen Channel Metadata Update Event
+
+When you join channel ,you will automatically Listen the Channel Metadata Update Event
+
+##### Channel Metadata Event
+
+**Description**
+
+it will occur when channel's metadata are updated, You need to complete this procedure yourself, and then you can handle this event when you are in the channel
+
+**Method**
+
+```objective-c
+- (void)metadataUpdate:(AgoraRtmMetadata* _Nonnull)data;
+```
+
+**Bassic Use**
+
+```objective-c
+// Create AgoraRtmChannel delegate:self
+AgoraRtmChannel* mRtmChannel = [rtmKit createChannelWithId:@“channelName” delegate:self];
+// Create three AgoraRtmMetadataItem instance
+AgoraRtmMetadataItem *item1 = [[AgoraRtmMetadataItem alloc] init];
+item1.key = @"nickname";
+item1.value = @"Tony Stark";
+
+AgoraRtmMetadataItem *item2 = [[AgoraRtmMetadataItem alloc] init];
+item2.key = @"gender";
+item2.value = @"male";
+
+AgoraRtmMetadataItem *item3 = [[AgoraRtmMetadataItem alloc] init];
+item3.key = @"age";
+item3.value = @"35";
+AgoraRtmChannel* mRtmChannel = [rtmKit createChannelWithId:@“channelName” delegate:self];
+// Add three instances to list
+NSArray *items = @[item1, item2, item3];
+
+// Create AgoraRtmMetadataOptions instance
+AgoraRtmMetadataOptions *options = [[AgoraRtmMetadataOptions alloc] init];
+options.majorRevision = -1;
+options.enableRecordTs = YES;
+options.enableRecordUserId = YES;
+
+// Invoke setLocalUserMetadataWithCompletion to set channel's metadata
+[mRtmChannel setChannelMetadataWithCompletion:items metadataOptions:options completion:^(AgoraRtmMetadataOperationErrorCode errorCode) {
+ if (errorCode == AgoraRtmMetadataOperationErrorOk) {
+ //The set operation is successful
+ } else {
+ //The set operation is failed;
+ }
+}];
+
+
+// Listen for user metadata updates
+- (void)mRtmChannel:(AgoraRtmChannelDelegate *)channel userMetadataUpdated:(AgoraRtmMetadata *)data {
+ //the user's metadata is updated
+ for (AgoraRtmMetadataItem *item in data.items) {
+ // process the metadataItems
+ }
+}
+```
+
+**Response**
+
+When this event occurs, you can recieve a type `RtmMetadata` for the specific `channel` which contains the following properties:
+
+| Property Name | Type | Description |
+|:-------------:|:----------------:| ---------------------------------------------------------------------------------- |
+| userId | NSString | Unique user identifier. |
+| `data` | AgoraRtmMetadata | `Metadata associated with a channel,see [AgoraRtmMetadata](#RtmMetadataItem) for more information. |
+
+> **Caution**: It should be noted that the returned data contains the full amount of data of the current channel, `Add`/`Set`/`Clear`/`Update`/`Delete` operation all will trigger this event, and you cannot distinguish which operation caused the current event. Need more features, you can use our new version 2.1.
+
+
\ No newline at end of file
diff --git a/shared/signaling/storage/reference/web.mdx b/shared/signaling/storage/reference/web.mdx
new file mode 100644
index 000000000..3f01ac11d
--- /dev/null
+++ b/shared/signaling/storage/reference/web.mdx
@@ -0,0 +1,8 @@
+
+
+### API reference
+
+* [Storage](../reference/api#storage)
+* [Lock](../reference/api#lock)
+
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/_stream-channel.mdx b/shared/signaling/stream-channel/_stream-channel.mdx
new file mode 100644
index 000000000..d7bca5e91
--- /dev/null
+++ b/shared/signaling/stream-channel/_stream-channel.mdx
@@ -0,0 +1,42 @@
+import * as data from '@site/data/variables';
+import Prerequisites from '@docs/shared/signaling/prerequisites.mdx';
+import Setup from '@docs/shared/common/project-setup/index.mdx';
+import ProjectImplement from '@docs/shared/signaling/stream-channel/project-implementation/index.mdx';
+import ProjectTest from '@docs/shared/signaling/stream-channel/project-test/index.mdx';
+import Reference from '@docs/shared/signaling/stream-channel/reference/index.mdx';
+
+
+Stream Channels are based on the room model. In a stream channel, users join a topic in order to send message to all users subscribed to that topic. enables each user to join up to one hundred channels at the same time. Each channel can have unlimited topics and up to 128 simultaneous users. Topics have a higher message transmission rate, greater message concurrency capability, and enable synchronous transmission of audio and video data. You use topics for Metaverse, AR/VR, interactive games, real-time collaboration, and parallel manipulation .
+
+For information about message channels where users communicate using a pub-sub model, see the [Message channel quickstart](/en/signaling/get-started/get-started-sdk).
+
+## Understand the tech
+
+The following figure shows the stream channel workflow:
+
+![Stream channel](/images/signaling/stream-channel-workflow.svg)
+
+## Prerequisites
+
+
+
+## Project setup
+
+
+
+## Implement communication in a stream channel
+
+This section shows how to use the to implement stream channels and presence in your .
+
+
+
+## Test stream channels
+
+
+
+## Reference
+
+This section contains information that completes the information in this page, or points you to documentation that explains other aspects to this
+product.
+
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/project-implementation/android.mdx b/shared/signaling/stream-channel/project-implementation/android.mdx
new file mode 100644
index 000000000..e67aac9a2
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/android.mdx
@@ -0,0 +1,19 @@
+
+1. **Set to connect to before you join a channel**
+
+ To access in a restricted network environment, call `setCloudProxy` and pass `0` as a parameter to select the automatic mode for transmission. The `setCloudProxy` method returns `0` upon
+ successful initiation of cloud proxy service.
+
+ To enable the service in your , in `/app/java/com.example./MainActivity`, add the following code after `agoraEngine = RtcEngine.create(config);`:
+
+ ``` java
+ // Start cloud proxy service and set automatic transmission mode.
+ int proxyStatus = agoraEngine.setCloudProxy(0);
+ if (proxyStatus == 0) {
+ showMessage("Proxy service started successfully");
+ } else {
+ showMessage("Proxy service failed with error :" + proxyStatus);
+ }
+ ```
+
+
diff --git a/shared/signaling/stream-channel/project-implementation/electron.mdx b/shared/signaling/stream-channel/project-implementation/electron.mdx
new file mode 100644
index 000000000..7419b6031
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/electron.mdx
@@ -0,0 +1,34 @@
+
+
+1. **Import the required modules**
+
+ In `preload.js`, add the following before `createAgoraRtcEngine,`:
+
+ ``` javascript
+ CloudProxyType,
+ ```
+
+2. **Enable the connection to **
+
+ To access in a restricted network environment, call `setCloudProxy` and set the force UDP transmission mode. To implement this logic, in `preload.js`, add the following code after
+ `agoraEngine.initialize({appId: appID});`:
+
+ ``` javascript
+ // Start cloud proxy service in the forced UDP mode.
+ agoraEngine.setCloudProxy(CloudProxyType.UdpProxy);
+ ```
+
+3. **Setup the cloud proxy callback function**
+
+ The triggers `onConnectionStateChanged` callback to indicate the successful initiation of cloud proxy service. To setup this callback, in `preload.js`, add the following code after `const EventHandles = {`:
+
+ ``` javascript
+ onConnectionStateChanged: (connection, state, reason) =>
+ {
+ if(reason == 0)
+ {
+ console.log("The SDK is connecting to the Agora edge server");
+ }
+ },
+ ```
+
diff --git a/shared/signaling/stream-channel/project-implementation/flutter.mdx b/shared/signaling/stream-channel/project-implementation/flutter.mdx
new file mode 100644
index 000000000..747fefa9e
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/flutter.mdx
@@ -0,0 +1,34 @@
+
+
+1. **Enable connection to **
+
+
+ To access in a restricted network environment, call `setCloudProxy` and set the transmission mode. To implement this logic, in `setupVoiceSDKEngine`, add the following code after calling `agoraEngine.initialize`:
+
+
+ To access in a restricted network environment, call `setCloudProxy` and set the transmission mode. To implement this logic, in `setupVideoSDKEngine`, add the following code after calling `agoraEngine.initialize`:
+
+
+ ``` dart
+ // Start cloud proxy service in forced UDP mode.
+ await agoraEngine.setCloudProxy(CloudProxyType.udpProxy);
+ ```
+
+1. **Set up the cloud proxy callback**
+
+
+ The triggers `onConnectionStateChanged` callback to indicate the successful initiation of cloud proxy service. To set up this callback, in `setupVoiceSDKEngine`, add the following code after `RtcEngineEventHandler(`:
+
+
+ The triggers `onConnectionStateChanged` callback to indicate the successful initiation of cloud proxy service. To set up this callback, in `setupVideoSDKEngine`, add the following code after `RtcEngineEventHandler(`:
+
+
+ ``` dart
+ onConnectionStateChanged: (RtcConnection connection, ConnectionStateType state,
+ ConnectionChangedReasonType reason) {
+ if (reason == ConnectionChangedReasonType.connectionChangedSettingProxyServer){
+ showMessage("Proxy settings changed");
+ }
+ },
+ ```
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/project-implementation/index.mdx b/shared/signaling/stream-channel/project-implementation/index.mdx
new file mode 100644
index 000000000..7b1ffffef
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/index.mdx
@@ -0,0 +1,20 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import ReactNative from './react-native.mdx';
+import Electron from './electron.mdx';
+import Flutter from './flutter.mdx';
+import MacOS from './macos.mdx'
+import Unity from './unity.mdx'
+import Windows from './windows.mdx';
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/project-implementation/ios.mdx b/shared/signaling/stream-channel/project-implementation/ios.mdx
new file mode 100644
index 000000000..e10531f95
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/ios.mdx
@@ -0,0 +1,8 @@
+import Code from './swift.mdx';
+
+
+
+
+
+
+
diff --git a/shared/signaling/stream-channel/project-implementation/macos.mdx b/shared/signaling/stream-channel/project-implementation/macos.mdx
new file mode 100644
index 000000000..11043d017
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/macos.mdx
@@ -0,0 +1,8 @@
+import Code from './swift.mdx';
+
+
+
+
+
+
+
diff --git a/shared/signaling/stream-channel/project-implementation/react-native.mdx b/shared/signaling/stream-channel/project-implementation/react-native.mdx
new file mode 100644
index 000000000..111b4383d
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/react-native.mdx
@@ -0,0 +1,32 @@
+
+
+1. **Import the required modules**
+
+ In `App.tsx`, add the following import before `createAgoraRtcEngine,`:
+
+ ``` javascript
+ CloudProxyType,
+ ```
+
+2. **Enable the connection to **
+
+ To access in a restricted network environment, call `setCloudProxy` and set the force UDP transmission mode. To implement this logic, in `App.tsx`, add the following code after
+ `agoraEngine.initialize({appId: appID});`:
+
+ ``` javascript
+ // Start cloud proxy service in the forced UDP mode.
+ agoraEngine.setCloudProxy(CloudProxyType.UdpProxy);
+ ```
+
+3. **Setup the cloud proxy callback function**
+
+ The triggers `onConnectionStateChanged` callback to indicate the successful initiation of cloud proxy service. To setup this callback, in `App.tsx`, add the following code after `onUserOffline: (_connection, remoteUid) => {`:
+
+ ``` javascript
+ onConnectionStateChanged(connection, state, reason) {
+ if (reason === 0) {
+ console.log('The SDK is connecting to the Agora edge server');
+ }
+ },
+ ```
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/project-implementation/swift.mdx b/shared/signaling/stream-channel/project-implementation/swift.mdx
new file mode 100644
index 000000000..0d060ad6d
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/swift.mdx
@@ -0,0 +1,15 @@
+
+1. **Start the service before you join a channel**
+
+ To access in a restricted network environment, call `setCloudProxy` and select the automatic mode for data transmission. The `setCloudProxy` method returns `0` upon successful initiation of
+ cloud proxy service.
+
+ To enable the cloud proxy service in your , in `ViewController`, add the following lines after `let option = AgoraRtcChannelMediaOptions()`:
+
+ ```swift
+ // Start cloud proxy service and select automatic mode for data transmission.
+ let status = agoraEngine.setCloudProxy(AgoraCloudProxyType.noneProxy)
+ if (status != 0) {
+ showMessage(title: "Cloud proxy status", text: "Proxy failed")
+ }
+ ```
diff --git a/shared/signaling/stream-channel/project-implementation/unity.mdx b/shared/signaling/stream-channel/project-implementation/unity.mdx
new file mode 100644
index 000000000..a31a4c9e6
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/unity.mdx
@@ -0,0 +1,19 @@
+
+1. **Set to connect to before you join a channel**
+
+ To access in a restricted network environment, call `SetCloudProxy` and pass `0` as a parameter to select the automatic mode for transmission. The `setCloudProxy` method returns `0` upon
+ successful initiation of cloud proxy service.
+
+ To enable the service in your , in your script file, locate `Start` and add the following code before `InitEventHandler();`:
+
+ ``` csharp
+ // Start cloud proxy service and set automatic transmission mode.
+ int proxyStatus = RtcEngine.SetCloudProxy(0);
+ if (proxyStatus == 0) {
+ Debug.Log("Proxy service started successfully");
+ } else {
+ Debug.Log("Proxy service failed with error :" + proxyStatus);
+ }
+ ```
+
+
diff --git a/shared/signaling/stream-channel/project-implementation/web.mdx b/shared/signaling/stream-channel/project-implementation/web.mdx
new file mode 100644
index 000000000..b8a923b77
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/web.mdx
@@ -0,0 +1,99 @@
+
+### Set up an event listener
+
+ Keep track of the users who join and leave topics, and events in the topic. In this implementation, we use the
+ event listener in [signalingManager](/en/signaling/get-started/get-started-sdk#handle-events).
+
+### Join and leave a channel
+
+ To create a new stream channel, call `createStreamChannel`. When you join the channel, you configure the features
+ you will use. For example, [Presence](/en/signaling/reference/api#presence), [Storage](/en/signaling/reference/api#storage), and [Lock](/en/signaling/reference/api#lock). You use the [RTC authentication
+ token](/en/signaling/get-started/authentication-workflow#authenticate-your--session) for the current UID to join the channel. To join a channel:
+
+ ```javascript
+ const streamChannelJoinAndLeave = async function (
+ isStreamChannelJoined,
+ uid,
+ streamChannelName
+ ) {
+ const token = await fetchRTCToken(uid, streamChannelName);
+ if (getSignalingStreamChannel() === null) {
+ streamChannel = await signalingManager
+ .getSignalingEngine()
+ .createStreamChannel(streamChannelName); // creates stream channel
+ }
+
+ if (isStreamChannelJoined === false) {
+ await streamChannel
+ .join({
+ token: token,
+ withPresence: true,
+ })
+ .then((response) => {
+ console.log(response);
+ });
+ } else {
+ streamChannel.leave().then((response) => {
+ console.log(response);
+ messageCallback("Left the channel: " + streamChannelName);
+ streamChannel = null;
+ });
+ }
+ };
+ ```
+
+
+### Join and leave a topic
+
+ A topic is a data stream management mechanism in stream channels. Users join, leave, subscribe, and
+ unsubscribe to topics. You join a topic to publish and recieve messages sent to all users in the topic.
+ You subscribe to listen to messages sent by publishers only. When a user joins or leaves a topic,
+ triggers an topic event notification. Users in the channel receive the notifications in real time. You use topic events
+ to track changes in topic status. To join a topic:
+
+ ```javascript
+ const topicJoinAndLeave = async function (isTopicJoined, topicName) {
+ if (isTopicJoined === false) {
+ await signalingManager.getSignalingStreamChannel().joinTopic(topicName).then((response) => {
+ messageCallback("Joined the topic: " + response.topicName);
+ });
+ } else {
+ signalingManager.getSignalingStreamChannel().leaveTopic(topicName).then((response) => {
+ console.log(response);
+ messageCallback("Left the topic: " + response.topicName);
+ });
+ }
+ };
+ ```
+ There is no limit to the number of subscribers and publishers in each topic. However, there are limitations to
+ the [number of topics and messages a user can subscribe to](/en/signaling/reference/limitations).
+
+### Publish messages to a topic
+
+ Messages are transmitted through the topic. After your user has joined a topic in a channel, you
+ publish messages to the topic. Publishers and subscribers receive the messages. To send a message:
+
+ ```javascript
+ const sendTopicMessage = function (message, topicName) {
+ if (message === "" || topicName === "") {
+ console.log(
+ "Make sure you specified a message and a topic to send messages"
+ );
+ return;
+ }
+ signalingManager.getSignalingStreamChannel().publishTopicMessage(topicName, message).then((response) => {
+ console.log(response);
+ messageCallback("Topic: " + topicName + ", Message:" + message);
+ });
+ };
+ ```
+
+### Subscribe to a topic
+
+ To receive messages published to a topic, you subscribe to the topic. To subscribe:
+
+ ```javascript
+ await signalingManager.getSignalingStreamChannel().subscribeTopic(topicName);
+ ```javascript
+
+
diff --git a/shared/signaling/stream-channel/project-implementation/windows.mdx b/shared/signaling/stream-channel/project-implementation/windows.mdx
new file mode 100644
index 000000000..730c1a8a7
--- /dev/null
+++ b/shared/signaling/stream-channel/project-implementation/windows.mdx
@@ -0,0 +1,21 @@
+
+1. **Set to connect to before you join a channel**
+
+ To access in a restricted network environment, call `setCloudProxy` and pass `0` as a parameter to select the automatic mode for transmission. The `setCloudProxy` method returns `0` upon
+ successful initiation of cloud proxy service.
+
+ To enable the service in your , in **Solution Explorer**, open `AgoraImplementationDlg.cpp` file and add the following code in `setupVideoSDKEngine()` after `agoraEngine->setClientRole(CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER);`:
+
+ ```cpp
+ // Start cloud proxy service and set automatic transmission mode
+ int proxyStatus = agoraEngine->setCloudProxy(CLOUD_PROXY_TYPE::NONE_PROXY);
+ if (proxyStatus == 0) {
+ AfxMessageBox(L"Proxy service started successfully");
+ } else {
+ CString message;
+ message.Format(_T("Proxy service failed with error: %d"), proxyStatus);
+ AfxMessageBox(message);
+ }
+ ```
+
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/project-test/android.mdx b/shared/signaling/stream-channel/project-test/android.mdx
new file mode 100644
index 000000000..02c772144
--- /dev/null
+++ b/shared/signaling/stream-channel/project-test/android.mdx
@@ -0,0 +1,23 @@
+
+
+3. In Android Studio, open `app/java/com.example./MainActivity`, and update `appId`, `channelName` and `token` with the values for your temporary token.
+
+4. Connect a physical Android device to your development device.
+
+5. In Android Studio, click **Run app**. A moment later you see the project installed on your device.
+
+ If this is the first time you run the project, grant microphone and camera access to your app.
+
+
+6. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+6. Click **Join** to start a call.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
diff --git a/shared/signaling/stream-channel/project-test/electron.mdx b/shared/signaling/stream-channel/project-test/electron.mdx
new file mode 100644
index 000000000..5ba3fe668
--- /dev/null
+++ b/shared/signaling/stream-channel/project-test/electron.mdx
@@ -0,0 +1,25 @@
+
+
+3. In _preload.js_, update `appID`, `channel` and `token` with your values.
+
+4. Run the app
+
+ Execute the following command in the terminal:
+
+ ```bash
+ npm start
+ ```
+ You see your app opens a window named **Get started with **.
+
+
+5. To join as a host, select **Host** and click **Join**.
+
+
+
+
+5. To connect to a channel, click **Join**.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
diff --git a/shared/signaling/stream-channel/project-test/flutter.mdx b/shared/signaling/stream-channel/project-test/flutter.mdx
new file mode 100644
index 000000000..62daa8bba
--- /dev/null
+++ b/shared/signaling/stream-channel/project-test/flutter.mdx
@@ -0,0 +1,23 @@
+
+
+3. In your IDE, open `main.dart`, and update `appId`, `channelName` and `token` with the values for your temporary token.
+
+4. Connect a test device to your development device.
+
+5. In your IDE, click *Run app* or execute `flutter run lib/main.dart`. A moment later you see the project installed on your device.
+
+ If this is the first time you run the project, grant microphone and camera access to your .
+
+
+6. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local stream is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+6. Click **Join** to start a call.
+
+
+ You see your connects to the from a restricted network environment using the cloud proxy service.
+
+
diff --git a/shared/signaling/stream-channel/project-test/index.mdx b/shared/signaling/stream-channel/project-test/index.mdx
new file mode 100644
index 000000000..f030391fe
--- /dev/null
+++ b/shared/signaling/stream-channel/project-test/index.mdx
@@ -0,0 +1,24 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import ReactNative from './react-native.mdx';
+import Electron from './electron.mdx';
+import Flutter from './flutter.mdx';
+import MacOS from './macos.mdx'
+import Unity from './unity.mdx'
+import Windows from './windows.mdx';
+import Test from '@docs/shared/common/project-test/index.mdx';
+
+To test this functionality:
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/project-test/ios.mdx b/shared/signaling/stream-channel/project-test/ios.mdx
new file mode 100644
index 000000000..f3011d0fa
--- /dev/null
+++ b/shared/signaling/stream-channel/project-test/ios.mdx
@@ -0,0 +1,24 @@
+
+
+3. In the `ViewController`, update `appID`, `channelName`, and `token` with the values from .
+
+4. Run your using either a physical or a simulator iOS device.
+
+ If this is the first time you run the project, grant microphone and camera access to your .
+
+ If you use an iOS simulator, you see the remote video only. You cannot see the local video stream because of Apple simulator hardware restrictions.
+
+
+5. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+
+You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
+
diff --git a/shared/signaling/stream-channel/project-test/macos.mdx b/shared/signaling/stream-channel/project-test/macos.mdx
new file mode 100644
index 000000000..268fbc910
--- /dev/null
+++ b/shared/signaling/stream-channel/project-test/macos.mdx
@@ -0,0 +1,20 @@
+
+
+3. In the `ViewController`, update `appID`, `channelName`, and `token` with the values from .
+
+4. Run your .
+
+
+5. Select an option and click **Join** to start a session.
+ * When you join as a **Host**, the local video is published and played in the .
+ * When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+
+You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
+
diff --git a/shared/signaling/stream-channel/project-test/react-native.mdx b/shared/signaling/stream-channel/project-test/react-native.mdx
new file mode 100644
index 000000000..ba94866cc
--- /dev/null
+++ b/shared/signaling/stream-channel/project-test/react-native.mdx
@@ -0,0 +1,38 @@
+
+3. In `App.tsx`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+1. Run your app:
+
+ - *Android*
+
+ 1. Enable Developer options on your Android device, and then connect it to your computer using a USB cable.
+ 1. Run `npx react-native run-android` in the project root directory.
+
+ - *iOS:*
+
+ 1. Open the `ProjectName/ios/ProjectName.xcworkspace` folder with Xcode.
+ 1. Connect your iOS device to your Mac using a USB cable.
+ 1. Click the **Build and run** button in Xcode.
+
+
+ If this is the first time you run the project, you need to grant microphone and camera access to your .
+
+
+5. Select an option and click **Join** to start a session.
+ - When you join as a **Host**, the local video is published and played in the .
+ - When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+5. Click **Join** to start a call.
+
+Now, you can see yourself on the device screen and talk to the remote user using your .
+
+
+
+5. Click **Join** to start a call.
+
+Now, you can talk to the remote user using your .
+
+
+
diff --git a/shared/signaling/stream-channel/project-test/unity.mdx b/shared/signaling/stream-channel/project-test/unity.mdx
new file mode 100644
index 000000000..1557aa689
--- /dev/null
+++ b/shared/signaling/stream-channel/project-test/unity.mdx
@@ -0,0 +1,17 @@
+
+
+3. In your script file, update `_appID`, `_channelName` and `_token` with the values for your temporary token.
+
+4. In **Unity Editor**, click **Play**. A moment later you see the running on your development device.
+
+
+5. To join as a host, select **Host** and click **Join**.
+
+
+
+5. To connect to a channel, click **Join**.
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/project-test/web.mdx b/shared/signaling/stream-channel/project-test/web.mdx
new file mode 100644
index 000000000..850445115
--- /dev/null
+++ b/shared/signaling/stream-channel/project-test/web.mdx
@@ -0,0 +1,11 @@
+
+
+4. **Test stream channels**:
+
+ 1. For each user, open the reference app in a new tab in your browser.
+
+ 1. Choose the **Stream Channels** example.
+
+ 1. Log in to , then send and receive messages using topics in channels.
+
+
diff --git a/shared/signaling/stream-channel/project-test/windows.mdx b/shared/signaling/stream-channel/project-test/windows.mdx
new file mode 100644
index 000000000..22eade59e
--- /dev/null
+++ b/shared/signaling/stream-channel/project-test/windows.mdx
@@ -0,0 +1,20 @@
+
+ 3. In `CAgoraImplementationDlg.h`, update `appId`, `channelName` and `token` with the values for your temporary token.
+
+ 4. In Visual Studio, click **Local Windows Debugger**. A moment later you see the project running on your development device.
+
+ If this is the first time you run the project, you need to grant microphone and camera access to your .
+
+
+ 5. Select an option and click **Join** to start a session.
+ - When you join as a **Host**, the local video is published and played in the .
+ - When you join as **Audience**, the remote stream is subscribed and played.
+
+
+
+ 5. Click **Join** to start a call. Now, you can see yourself on the device screen and talk to the remote user using your .
+
+
+ You see your starts the proxy service and magically connects to the which was not possible in a restricted network environment.
+
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/reference/android.mdx b/shared/signaling/stream-channel/reference/android.mdx
new file mode 100644
index 000000000..30bc8406d
--- /dev/null
+++ b/shared/signaling/stream-channel/reference/android.mdx
@@ -0,0 +1,12 @@
+
+
+### API reference
+
+
+* setCloudProxy
+
+
+* setCloudProxy
+
+
+
diff --git a/shared/signaling/stream-channel/reference/electron.mdx b/shared/signaling/stream-channel/reference/electron.mdx
new file mode 100644
index 000000000..5afec5a3a
--- /dev/null
+++ b/shared/signaling/stream-channel/reference/electron.mdx
@@ -0,0 +1,19 @@
+
+
+### API reference
+
+
+
+- setCloudProxy
+- onConnectionStateChanged
+
+
+
+
+- setCloudProxy
+- onConnectionStateChanged
+
+
+
+
+
diff --git a/shared/signaling/stream-channel/reference/flutter.mdx b/shared/signaling/stream-channel/reference/flutter.mdx
new file mode 100644
index 000000000..9e2f904d0
--- /dev/null
+++ b/shared/signaling/stream-channel/reference/flutter.mdx
@@ -0,0 +1,17 @@
+
+
+### API reference
+
+
+* setCloudProxy
+
+* onConnectionStateChanged
+
+
+
+* setCloudProxy
+
+* onConnectionStateChanged
+
+
+
diff --git a/shared/signaling/stream-channel/reference/index.mdx b/shared/signaling/stream-channel/reference/index.mdx
new file mode 100644
index 000000000..7ae892546
--- /dev/null
+++ b/shared/signaling/stream-channel/reference/index.mdx
@@ -0,0 +1,19 @@
+import Android from './android.mdx';
+import Ios from './ios.mdx';
+import Web from './web.mdx';
+import ReactNative from './react-native.mdx';
+import Electron from './electron.mdx';
+import Flutter from './flutter.mdx';
+import MacOS from './macos.mdx'
+import Unity from './unity.mdx'
+import Windows from './windows.mdx';
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/reference/ios.mdx b/shared/signaling/stream-channel/reference/ios.mdx
new file mode 100644
index 000000000..103b2e211
--- /dev/null
+++ b/shared/signaling/stream-channel/reference/ios.mdx
@@ -0,0 +1,7 @@
+
+
+#### API reference
+
+* setCloudProxy
+
+
diff --git a/shared/signaling/stream-channel/reference/macos.mdx b/shared/signaling/stream-channel/reference/macos.mdx
new file mode 100644
index 000000000..93b97ab88
--- /dev/null
+++ b/shared/signaling/stream-channel/reference/macos.mdx
@@ -0,0 +1,7 @@
+
+
+#### API reference
+
+* setCloudProxy
+
+
diff --git a/shared/signaling/stream-channel/reference/react-native.mdx b/shared/signaling/stream-channel/reference/react-native.mdx
new file mode 100644
index 000000000..ad1b9afeb
--- /dev/null
+++ b/shared/signaling/stream-channel/reference/react-native.mdx
@@ -0,0 +1,14 @@
+
+
+### API reference
+
+
+- setCloudProxy
+- onConnectionStateChanged
+
+
+- setCloudProxy
+- onConnectionStateChanged
+
+
+
\ No newline at end of file
diff --git a/shared/signaling/stream-channel/reference/unity.mdx b/shared/signaling/stream-channel/reference/unity.mdx
new file mode 100644
index 000000000..5d55f0d71
--- /dev/null
+++ b/shared/signaling/stream-channel/reference/unity.mdx
@@ -0,0 +1,16 @@
+import * as data from '@site/data/variables';
+
+
+#### API references
+
+
+- SetCloudProxy
+- CLOUD_PROXY_TYPE
+
+
+
+- SetCloudProxy
+- CLOUD_PROXY_TYPE
+
+
+
diff --git a/shared/signaling/stream-channel/reference/web.mdx b/shared/signaling/stream-channel/reference/web.mdx
new file mode 100644
index 000000000..5163730c7
--- /dev/null
+++ b/shared/signaling/stream-channel/reference/web.mdx
@@ -0,0 +1,84 @@
+
+
+### Tracking users in a Stream Channel between disconnection and reconnection
+
+After you start listening to topic event notifications, if you are disconnected and reconnect, you receive the topic `SNAPSHOT` event. To obtain the list of users who joined or left the Stream Channel between disconnection and reconnection, refer to the following code example to generate a local cache and compare it with the user list in the `SNAPSHOT` event.
+
+* Users with more events than in the local cache are users who joined the channel between disconnection and reconnection.
+* Users with fewer events than in the local cache are users who left the channel between disconnection and reconnection.
+
+ ```js
+ // global variable
+ const
+ channelTopics: Map> = new Map();
+ // event handler
+ const rtmConfig = {};
+ const rtm = new RTM('appid', 'uid', rtmConfig);
+ rtm.addEventListener('topic', (topicEvent) => {
+ console.log(topicEvent, 'topic');
+
+ const topicsCache: Map = channelTopics.get(topicEvent.channelName) ?? new Map();
+ const remoteLeaved: Map = new Map();
+ const remoteJoined: Map = new Map();
+ const { publisher: user, channelName } = topicEvent;
+ if (topicEvent.eventType === 'SNAPSHOT') {
+ topicEvent.topicInfos.forEach(({
+ publishers,
+ topicName }) => {
+ remoteJoined.set(topicName, []);
+ remoteLeaved.set(topicName, []);
+ const topicDetailsByCache = topicsCache.get(topicName) ?? [];
+
+ // removed
+ topicDetailsByCache.forEach(({ publisherMeta, publisherUserId: targetUid }) => {
+ if (!publishers.some(({ publisherUserId: eventUid }) => targetUid === eventUid)) {
+ remoteLeaved.get(topicName)?.push({ publisherUserId: targetUid, publisherMeta });
+ topicDetailsByCache.filter(({ publisherUserId: cacheUid }) => cacheUid !== targetUid);
+ }
+ });
+ // added
+ publishers.forEach(({ publisherMeta, publisherUserId: eventUid }) => {
+ if (!topicDetailsByCache.some(({ publisherUserId: cacheUid }) => {
+ return eventUid === cacheUid;
+ })) {
+ remoteJoined.get(topicName)?.push({ publisherUserId: eventUid, publisherMeta });
+ topicDetailsByCache.push({ publisherUserId: eventUid, publisherMeta })
+ }
+ });
+ topicsCache.set(topicName, topicDetailsByCache);
+ });
+ } else {
+ // your code for handling the updated event
+ topicEvent.topicInfos.forEach(({ topicName, publishers }) => {
+ const topicDetailsByCache = topicsCache.get(topicName) ?? [];
+ publishers.forEach(({ publisherMeta, publisherUserId }) => {
+ if (user === publisherUserId) {
+ switch (topicEvent.eventType) {
+ case 'REMOTE_JOIN': {
+ topicDetailsByCache.push({ publisherMeta, publisherUserId });
+ break;
+ }
+ case 'REMOTE_LEAVE': {
+ topicDetailsByCache.filter(({publisherUserId: uid}) =>
+ uid !== publisherUserId
+ )
+ break;
+ }
+ }
+ topicsCache.set(topicName, topicDetailsByCache);
+ }
+ });
+ })
+ }
+ channelTopics.set(channelName, topicsCache);
+ console.log({ remoteJoined, remoteLeaved, channelTopics, channelName }, 'topic diff for debug');
+ }
+ );
+ ```
+
+For more information, see:
+
+- [ API reference](/en/signaling/reference/api)
+- [API limitations](/en/signaling/reference/limitations)
+
+
diff --git a/shared/signaling/stream-channel/reference/windows.mdx b/shared/signaling/stream-channel/reference/windows.mdx
new file mode 100644
index 000000000..6ab51cd60
--- /dev/null
+++ b/shared/signaling/stream-channel/reference/windows.mdx
@@ -0,0 +1,7 @@
+
+
+#### API reference
+
+* setCloudProxy
+
+
\ No newline at end of file
diff --git a/shared/variables/global.js b/shared/variables/global.js
index 6709b9517..9f3424694 100644
--- a/shared/variables/global.js
+++ b/shared/variables/global.js
@@ -71,6 +71,7 @@ export const CONSOLE = `${COMPANY} Console`;
export const TOKEN = 'token';
export const ENGINE = `${COMPANY} Engine`;
+
export const RTE = 'Real-Time Engagement';
export const RTEC = `${RTE} Core`;
export const RTES = `${RTE} SDK`;
@@ -105,11 +106,12 @@ export const AV = 'Audio/Video';
export const MESS = 'Signaling';
export const MESS_SDK = `${MESS} SDK`;
+export const MESS_ENGINE = `${MESS}Engine`;
export const EASEMOB_SDK = `Easemob IM SDK`;
export const NCS = `NCS`;
export const NCS_LONG = `Notifications`;
export const SIG = `${MESS}`;
-export const SIG_RELEASE_API = `1.x`;
+export const SIG_RELEASE_API = `2.x`;
export const SIG_SDK_API_REF_ROOT = `${API_ROOT}/signaling-sdk`;
export const SIG_SDK_API_ANDROID = `${SIG_SDK_API_REF_ROOT}/android/${SIG_RELEASE_API}`;
export const SIG_SDK_API_WIN_CPP = `${SIG_SDK_API_REF_ROOT}/windows-cpp/${SIG_RELEASE_API}`;
@@ -246,6 +248,9 @@ export const DEMO_BASIC_VIDEO_CALL_URL =
export const DEMO_PAGE_LINK = ` web demo`;
+export const DEMO_TOKEN_BUILDER_URL =
+ 'https://webdemo.agora.io/token-builder';
+
export const AGORA_DYNAMIC_KEY_CODE_BASE_URL =
'https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey';
diff --git a/shared/variables/product.js b/shared/variables/product.js
index a681bc8f3..a16379ade 100644
--- a/shared/variables/product.js
+++ b/shared/variables/product.js
@@ -55,6 +55,9 @@ const data = {
SDK: 'Signaling SDK',
PRODUCT: 'Signalling',
STREAM: 'interactive live streaming or broadcast streaming',
+ PRODUCT: 'Signalling',
+ IOS_PACKAGE_NAME: 'AgoraRTM_iOS',
+ MACOS_PACKAGE_NAME: 'AgoraRTM_macOS'
},
'server-gateway': {
diff --git a/signaling/develop/_category_.json b/signaling/develop/_category_.json
index bee160082..8d04cb259 100644
--- a/signaling/develop/_category_.json
+++ b/signaling/develop/_category_.json
@@ -1,6 +1,6 @@
{
"position": 3,
- "label": "Develop",
+ "label": "Core functionality",
"collapsible": true,
"link": null
}
diff --git a/signaling/develop/authentication-workflow.mdx b/signaling/develop/authentication-workflow.mdx
deleted file mode 100644
index 52a19f223..000000000
--- a/signaling/develop/authentication-workflow.mdx
+++ /dev/null
@@ -1,579 +0,0 @@
----
-title: 'Secure authentication with tokens'
-sidebar_position: 1
-type: docs
-description: >
- Create an Signaling token server and a Signaling client app.
----
-
-
-export const toc = [{}];
-
-
-Authentication is the act of validating the identity of each user before they access your system. Agora uses digital tokens to authenticate users and their privileges before they access an Agora service, such as joining an Agora call, or logging into the Signaling system.
-
-This document shows you how to create a token server and a client app. The client app retrieves a token from the token server. This token authenticates the current user when the user accesses .
-
-## Understand the tech
-
-The following figure shows the steps in the authentication flow:
-
-![ token authentication flow](https://web-cdn.agora.io/docs-files/1624939517653)
-
-a token is a dynamic key generated on your app server that is valid for 24 hours. When your users log in to from your app client, validates the token and reads the user and project information stored in the token. a token contains the following information:
-
-- The App ID of your Agora project
-
-- The App Certificate of your Agora project
-
-- The user ID of the user to be authenticated
-
-- The Unix timestamp when the token expires
-
-## Prerequisites
-
-In order to follow this procedure you must have the following:
-
-- A valid [ account](../reference/manage-agora-account#_create_an_agora_account).
-
-- An Agora project with the [App Certificate](../reference/manage-agora-account#_get_the_app_certificate) enabled.
-
-- [Golang](https://golang.org/) 1.14+ with GO111MODULE set to on.
-
-- If you are using Go 1.16+, GO111MODULE is on by default. See [this blog](https://blog.golang.org/go116-module-changes) for details.
-
-- [npm](https://www.npmjs.com/get-npm) and a [supported browsers](../overview/supported-platforms).
-
-- For AccessToken2, v1.5.0 or greater.
-
-## Implement the authentication flow
-
-This section shows you how to supply and consume a token that gives rights to specific functionality to authenticated users using the [source code](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey) provided by Agora.
-
-### Get the App ID and App Certificate
-
-This section shows you how to get the security information needed to generate a token, including the App ID and App Certificate of your project.
-
-#### 1. Get the App ID
-
-Agora automatically assigns each project an App ID as a unique identifier.
-
-To copy this App ID, find your project on the Project Management page in Agora Console, and click the plus icon in the App ID column.
-
-#### 2. Get the App Certificate
-
-To get an App Certificate, do the following:
-
-1. On the Project Management page, click **Config** for the project you want to use.
-![1641971710869](https://web-cdn.agora.io/docs-files/1641971710869)
-
-2. Click the copy icon under **Primary Certificate**.
-![1637660100222](https://web-cdn.agora.io/docs-files/1637660100222)
-
-### Deploy a token server
-
-Token generators create the tokens requested by your client app to enable secure access to Agora Platform. To serve these tokens you deploy a generator in your security infrastructure.
-
-In order to show the authentication workflow, this section shows how to build and run a token server written in Golang on your local machine.
-
-**This sample server is for demonstration purposes only. Do not use it in a production environment.**
-
-1. Create a file, `server.go`, with the following content. Then replace `` and `` with your App ID and App Certificate.
-
- Implement either:
-
- - **AccessToken2**
-
- ```go
- package main
-
- import (
- rtmtokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtmtokenbuilder2"
- "fmt"
- "log"
- "net/http"
- "time"
- "encoding/json"
- "errors"
- "strconv"
- )
-
- type rtm_token_struct struct{
- Uid_rtm string `json:"uid"`
- }
-
- var rtm_token string
- var rtm_uid string
-
- func generateRtmToken(rtm_uid string){
-
- appID := "Your_App_ID"
- appCertificate := "Your_Certificate"
- expireTimeInSeconds := uint32(3600)
- currentTimestamp := uint32(time.Now().UTC().Unix())
- expireTimestamp := currentTimestamp + expireTimeInSeconds
-
- result, err := rtmtokenbuilder.BuildToken(appID, appCertificate, rtm_uid, expireTimestamp)
- if err != nil {
- fmt.Println(err)
- } else {
- fmt.Printf("Rtm Token: %s\n", result)
-
- rtm_token = result
-
- }
- }
-
- func rtmTokenHandler(w http.ResponseWriter, r *http.Request){
- w.Header().Set("Content-Type", "application/json;charset=UTF-8")
- w.Header().Set("Access-Control-Allow-Origin", "*")
- w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS");
- w.Header().Set("Access-Control-Allow-Headers", "*");
-
- if r.Method == "OPTIONS" {
- w.WriteHeader(http.StatusOK)
- return
- }
-
- if r.Method != "POST" && r.Method != "OPTIONS" {
- http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
- return
- }
-
- var t_rtm_str rtm_token_struct
- var unmarshalErr *json.UnmarshalTypeError
- str_decoder := json.NewDecoder(r.Body)
- rtm_err := str_decoder.Decode(&t_rtm_str)
-
- if (rtm_err == nil) {
- rtm_uid = t_rtm_str.Uid_rtm
- }
-
- if (rtm_err != nil) {
- if errors.As(rtm_err, &unmarshalErr){
- errorResponse(w, "Bad request. Please check your params.", http.StatusBadRequest)
- } else {
- errorResponse(w, "Bad request.", http.StatusBadRequest)
- }
- return
- }
-
- generateRtmToken(rtm_uid)
- errorResponse(w, rtm_token, http.StatusOK)
- log.Println(w, r)
- }
-
-
- func errorResponse(w http.ResponseWriter, message string, httpStatusCode int){
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("Access-Control-Allow-Origin", "*")
- w.WriteHeader(httpStatusCode)
- resp := make(map[string]string)
- resp["token"] = message
- resp["code"] = strconv.Itoa(httpStatusCode)
- jsonResp, _ := json.Marshal(resp)
- w.Write(jsonResp)
-
- }
-
- func main(){
- // Handling routes
- // token from uid
- http.HandleFunc("/fetch_rtm_token", rtmTokenHandler)
-
- fmt.Printf("Starting server at port 8082\n")
-
- if err := http.ListenAndServe(":8082", nil); err != nil {
- log.Fatal(err)
- }
- }
- ```
- - **AccessToken**
-
- ```go
- package main
-
- import (
- rtmtokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtmTokenBuilder"
- "fmt"
- "log"
- "net/http"
- "time"
- "encoding/json"
- "errors"
- "strconv"
- )
-
- type rtm_token_struct struct{
- Uid_rtm string `json:"uid"`
- }
-
- var rtm_token string
- var rtm_uid string
-
- func generateRtmToken(rtm_uid string){
-
- appID := "Your_App_ID"
- appCertificate := "Your_Certificate"
- expireTimeInSeconds := uint32(3600)
- currentTimestamp := uint32(time.Now().UTC().Unix())
- expireTimestamp := currentTimestamp + expireTimeInSeconds
-
- result, err := rtmtokenbuilder.BuildToken(appID, appCertificate, rtm_uid, rtmtokenbuilder.RoleRtmUser, expireTimestamp)
- if err != nil {
- fmt.Println(err)
- } else {
- fmt.Printf("Rtm Token: %s\n", result)
-
- rtm_token = result
-
- }
- }
-
- func rtmTokenHandler(w http.ResponseWriter, r *http.Request){
- w.Header().Set("Content-Type", "application/json;charset=UTF-8")
- w.Header().Set("Access-Control-Allow-Origin", "*")
- w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS");
- w.Header().Set("Access-Control-Allow-Headers", "*");
-
- if r.Method == "OPTIONS" {
- w.WriteHeader(http.StatusOK)
- return
- }
-
- if r.Method != "POST" && r.Method != "OPTIONS" {
- http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
- return
- }
-
- var t_rtm_str rtm_token_struct
- var unmarshalErr *json.UnmarshalTypeError
- str_decoder := json.NewDecoder(r.Body)
- rtm_err := str_decoder.Decode(&t_rtm_str)
-
- if (rtm_err == nil) {
- rtm_uid = t_rtm_str.Uid_rtm
- }
-
- if (rtm_err != nil) {
- if errors.As(rtm_err, &unmarshalErr){
- errorResponse(w, "Bad request. Please check your params.", http.StatusBadRequest)
- } else {
- errorResponse(w, "Bad request.", http.StatusBadRequest)
- }
- return
- }
-
- generateRtmToken(rtm_uid)
- errorResponse(w, rtm_token, http.StatusOK)
- log.Println(w, r)
- }
-
- func errorResponse(w http.ResponseWriter, message string, httpStatusCode int){
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("Access-Control-Allow-Origin", "*")
- w.WriteHeader(httpStatusCode)
- resp := make(map[string]string)
- resp["token"] = message
- resp["code"] = strconv.Itoa(httpStatusCode)
- jsonResp, _ := json.Marshal(resp)
- w.Write(jsonResp)
-
- }
-
- func main(){
- // Handling routes
- // Signaling token from Signaling uid
- http.HandleFunc("/fetch_rtm_token", rtmTokenHandler)
-
- fmt.Printf("Starting server at port 8082\n")
-
- if err := http.ListenAndServe(":8082", nil); err != nil {
- log.Fatal(err)
- }
- }
- ```
-
-2. A `go.mod` file defines this module’s import path and dependency requirements. To create the `go.mod` for your token server, run the following command:
-
- ```shell
- $ go mod init sampleServer
- ```
-
-3. Get dependencies by running the following command:
-
- ```shell
- $ go get
- ```
-
-4. Start the server by running the following command:
-
- ```shell
- $ go run server.go
- ```
-
-### Use tokens for user authentication
-
-This section uses the Web client as an example to show how to use a token for client-side user authentication.
-
-In order to show the authentication workflow, this section shows how to build and run a Web client on your local machine.
-
-**This sample client is for demonstration purposes only. Do not use it in a production environment.**
-
-1. Create the project structure of the Web client with a folder including the following files.
-
- - `index.html`: User interface
-
- - `client.js`: App logic with Agora
-
-2. Download [Agora for Web](../reference/downloads). Save the JS file in `libs` to your project directory.
-
-3. In `index.html`, add the following code to include the app logic in the UI, then replace `` with the path of the JS file you saved in step 2.
-
- ```html
-
-
- Signaling token demo
-
-
-
-
Token demo
-
-
-
-
- ```
-
-4. Create the app logic by editing `client.js` with the following content. Then replace `` with your App ID. The App ID must match the one in the server. You also need to replace `` with the host URL and port of the local Golang server you have just deployed, such as `10.53.3.234:8082`.
-
- ```js
- // Parameters for the login method
- let options = {
- token: "",
- uid: ""
- }
-
- // Whether to stop the token renew loop
- let stopped = false
-
- function sleep (time) {
- return new Promise((resolve) => setTimeout(resolve, time));
- }
-
- function fetchToken(uid) {
-
- return new Promise(function (resolve) {
- axios.post('http:///fetch_rtm_token', {
- uid: uid,
- }, {
- headers: {
- 'Content-Type': 'application/json; charset=UTF-8'
- }
- })
- .then(function (response) {
- const token = response.data.token;
- resolve(token);
- })
- .catch(function (error) {
- console.log(error);
- });
- })
- }
-
- async function loginRTM()
- {
-
- // Your app ID
- const appID = ""
-
- // Initialize the client
- const client = AgoraRTM.createInstance(appID)
-
- // Display connection state changes
- client.on('ConnectionStateChanged', function (state, reason) {
- console.log("State changed To: " + state + " Reason: " + reason)
- })
-
- // Set Signaling user ID
- options.uid = "1234"
- // Get Token
- options.token = await fetchToken(options.uid)
- // Log in to Signaling
- await client.login(options)
-
- while (!stopped)
- {
- // Renew a token every 30 seconds for demonstration purposes.
- // Agora recommends that you renew a token regularly, such as every hour, in production.
- await sleep(30000)
- options.token = await fetchToken(options.uid)
- client.renewToken(options.token)
-
- let currentDate = new Date();
- let time = currentDate.getHours() + ":" + currentDate.getMinutes() + ":" + currentDate.getSeconds();
-
- console.log("Renew Signaling token at " + time)
- }
-
- }
-
- loginRTM()
- ```
-
-5. Open `index.html` with a supported browser to perform the following actions:
-
-- Successfully logging in to .
-
-- Renewing a token every 30 seconds.
-
-## Reference
-
-This section introduces token generator libraries, version requirements, and related documents about tokens.
-
-### Token generator libraries
-
-Agora provides an open-source [AgoraDynamicKey](https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey) repository on GitHub, which enables you to generate tokens on your server with programming languages such as C++, Java, and Go.
-
-- **AccessToken2**
-
- | Language | Algorithm | Core method | Sample code |
- | -------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
- | C++ | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtmTokenBuilder2.h) | [RtmTokenBuilder2Sample.cpp](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtmTokenBuilder2Sample.cpp) |
- | Go | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/rtmtokenbuilder2/rtmtokenbuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/sample/rtmtokenbuilder2/sample.go) |
- | Java | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/rtm/RtmTokenBuilder2.java) | [RtmTokenBuilder2Sample.java](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtmTokenBuilder2Sample.java) |
- | PHP | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtmTokenBuilder2.php) | [RtmTokenBuilder2Sample.php](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/sample/RtmTokenBuilder2Sample.php) |
- | Python 2 | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtmTokenBuilder2.py) | [RtmTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/sample/RtmTokenBuilder2Sample.py) |
- | Python 3 | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtmTokenBuilder2.py) | [RtmTokenBuilder2Sample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/sample/RtmTokenBuilder2Sample.py) |
-
-- **AccessToken**
-
- | Language | Algorithm | Core method | Sample code |
- |----------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
- | C++ | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/src/RtmTokenBuilder.h) | [RtmTokenBuilderSample.cpp](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/cpp/sample/RtmTokenBuilderSample.cpp) |
- | Go | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/src/RtmTokenBuilder/RtmTokenBuilder.go) | [sample.go](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/go/sample/RtmTokenBuilder/sample.go) |
- | Java | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/rtm/RtmTokenBuilder.java) | [RtmTokenBuilderSample.java](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/java/src/main/java/io/agora/sample/RtmTokenBuilderSample.java) |
- | Node.js | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/src/RtmTokenBuilder.js) | [RtmTokenBuilderSample.js](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/nodejs/sample/RtmTokenBuilderSample.js) |
- | PHP | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/src/RtmTokenBuilder.php) | [RtmTokenBuilderSample.php](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/php/sample/RtmTokenBuilderSample.php) |
- | Python 2 | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/src/RtmTokenBuilder.py) | [RtmTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python/sample/RtmTokenBuilderSample.py) |
- | Python 3 | HMAC-SHA256 | [buildToken](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/src/RtmTokenBuilder.py) | [RtmTokenBuilderSample.py](https://github.com/AgoraIO/Tools/blob/master/DynamicKey/AgoraDynamicKey/python3/sample/RtmTokenBuilderSample.py) |
-
-### API reference
-
-This section introduces the method to generate a token. Take C++ as an example:
-
-- **AccessToken2**
-
- ```go
- func BuildToken(appId string, appCertificate string, userId string, expire uint32) (string, error) {
- token := accesstoken.NewAccessToken(appId, appCertificate, expire)
- serviceRtm := accesstoken.NewServiceRtm(userId)
- serviceRtm.AddPrivilege(accesstoken.PrivilegeLogin, expire)
- token.AddService(serviceRtm)
- return token.Build()
- }
- ```
-
- | Parameter | Description |
- | :----------------- | :----------------------------------------------------------- |
- | `appId` | The App ID of your Agora project. |
- | `appCertificate` | The App Certificate of your Agora project. |
- | `userId` | The user ID of the system. You need specify the user ID yourself. See the userId parameter of the login method for supported character sets. |
- | `expire` | The duration (in seconds) from the generation of AccessToken2 to the expiration of AccessToken2. For example, if you set it as 600, the AccessToken2 expires 10 minutes after generation. An AccessToken2 is valid for a maximum of 24 hours. If you set it to a duration longer than 24 hours, the AccessToken2 still expires after 24 hours. If you set it to 0, the AccessToken2 expires immediately. |
-
-
-- **AccessToken**
- ```cpp
- static std::string buildToken(const std::string& appId,
- const std::string& appCertificate,
- const std::string& userAccount,
- RtmUserRole userRole,
- uint32_t privilegeExpiredTs = 0);
- ```
-
- | Parameter | Description |
- |--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
- | appId | The App ID of your Agora project. |
- | appCertificate | The App Certificate of your Agora project. |
- | userAccount | The user ID of . You need specify the user ID yourself. See the userId parameter of the login method for supported character sets. |
- | role | The user role. Agora supports only one user role. Set the value as the default value `Rtm_User`. |
- | privilegeExpiredTs | The Unix timestamp (s) when the token expires, represented by the sum of the current timestamp and the valid time of the token. This parameter is currently invalid. You can ignore this parameter. a token is valid for 24 hours. |
-
-### Upgrade AccessToken2
-
-This section introduces how to upgrade from AccessToken to AccessToken2 by example.
-
-#### Prerequisites
-
-- You have deployed a token server and a web client for AccessToken in a previous version.
-- You have integrated an [SDK version](#sdk-version) that supports AccessToken2.
-
-#### Update the token server
-
-1. Replace the `rtmtokenbuilder` import statement:
-
-```go
-// Replace "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/RtmTokenBuilder"
-// with "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtmtokenbuilder2".
-import (
- rtmtokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtmtokenbuilder2"
- "fmt"
- "log"
- "net/http"
- "time"
- "encoding/json"
- "errors"
- "strconv"
-)
-```
-
-2. Update the `BuildToken` function:
-
-```go
-// Previously, it is `result, err := rtmtokenbuilder.BuildToken(appID, appCertificate, rtm_uid, rtmtokenbuilder.RoleRtmUser, expireTimestamp)`.
-// Now, remove `rtmtokenbuilder.RoleRtmUser`.
-result, err := rtmtokenbuilder.BuildToken(appID, appCertificate, rtm_uid, expireTimestamp)
-```
-
-#### Test the AccessToken2 server
-
-The client does not require any updates; however, the [expiration logic](#expiration) changes accordingly.
-
-To use AccessToken2 in server-side, refer to [RESTful API Authentication](../reference/restful-authentication#sample-code-for-accesstoken2) and authenticate with new request headers.
-
-
-### Considerations
-
-#### User ID
-
-The user ID that you use to generate the token must be the same as the one you use to log in to .
-
-#### App Certificate and token
-
-To use the token for authentication, you need to enable the App Certificate for your project on Console. Once a project has enabled the App Certificate, you must use tokens to authenticate its users.
-
-### Token expiration
-
-- **AccessToken2**
-
- AccessToken2 allows you to specify the validity period of an token in seconds based on your business requirements. The validity period can be a maximum of 24 hours.
-
- When a token is due to expire in 30 seconds, the triggers the `onTokenPrivilegeWillExpire` callback. Upon receiving this callback, you can generate a new token on your app server and call `renewToken` to pass the new token to the SDK.
-
- When an token expires, the subsequent logic varies depending on the connection state of the SDK:
-
- - If the is in the `CONNECTION_STATE_CONNECTED` state, users receive the `onTokenExpired` callback and the `onConnectionStateChanged` callback caused by `CONNECTION_CHANGE_REASON_TOKEN_EXPIRED (9)`, notifying them that the connection state of the SDK switches to `CONNECTION_STATE_ABORTED`. In this case, users need to log in again via the `login` method.
- - If the is in the `CONNECTION_STATE_RECONNECTING` state, users receive the `onTokenExpired` callback when the network reconnects. In this case, users need to renew the token via the `renewToken` method.
-
- Although you can use the onTokenPrivilegeWillExpire and onTokenExpired callbacks to handle token expiration conditions, Agora recommends that you regularly renew the token (such as every hour) to keep the token valid.
-
- The names of methods, callbacks, and enums mentioned in the above section only apply to C++. Refer to the API documentation for names in other platforms.
-
-- **AccessToken**
-
- a token is valid for 24 hours.
-
- When the is in the `CONNECTION_STATE_CONNECTED` state, the user remains online even if the token expires. If a user logs in with an expired token, the returns the `LOGIN_ERR_TOKEN_EXPIRED` error.
-
- The triggers the `onTokenExpired` callback only when a token expires and the is in the `CONNECTION_STATE_RECONNECTING` state. The callback is triggered only once. Upon receiving this callback, you can generate a new token on your app server, and call `renewToken` to pass the new token to the SDK.
-
-
diff --git a/signaling/develop/best-practice.mdx b/signaling/develop/best-practice.mdx
deleted file mode 100644
index 02155d5a6..000000000
--- a/signaling/develop/best-practice.mdx
+++ /dev/null
@@ -1,123 +0,0 @@
----
-title: 'Integration best practice'
-sidebar_position: 6
-type: docs
-description: >
- Best practice when you integrate Signaling SDK into your app.
----
-
-
-export const toc = [{}];
-
-
-An token stays valid for 24 hours. Agora recommends that you generate the token from your server and call `renewToken` to update the token for the SDK at a fixed interval, such as one hour. See [Generate the token](authentication-workflow) to learn more about generating a token from your server.
-
-The following code example shows how to update the token at a fixed interval:
-
-```cpp
-// C++
-// Monitor the callback for token expiration
-class MyHandler : public IRtmServiceEventHandler {
-public:
- void onRenewTokenResult(const char* token, RENEW_TOKEN_ERR_CODE errorCode)
- {
- if (errorCode == RENEW_TOKEN_ERR_OK) {
- token_ = errorCode;
- }
- else {
- token_ = "";
- }
- }
-
- void setToken(const std::string& token) {
- token_ = token;
- }
-
- std::string getNewToken()
- {
- return token_;
- }
-private:
- std::string token_;
-};
-
-// Get the token generated from the server and update the token for the SDK
-void renewTokenDemoCode() {
- auto client = createRtmService();
- std::string appId = "";
- std::string token = "";
- std::string userId = "";
- bool stopped = false;
- MyHandler handler;
- handler.setToken(token);
- client->initialize("appId", &handler);
- client->login(token.c_str(), userId.c_str());
- // Wait for login success
- sleep(10);
-
- // Token Update token in a single thread
- while(!stopped) {
- // Update token every hour
- sleep(60 * 60);
- // Error handling
- if (handler.getNewToken().empty()) {
- break;
- }
- client->renewToken(handler.getNewToken().c_str());
- }
- client->logout();
- client->release();
-}
-```
-
-## Manage your resources
-
-### Release unneeded resources in time
-
-**(C++ SDK, Java SDK)** Memory leaks may occur if you do not release resources in time. Although the GC (Garbage Collection) mechanism of Java can recycle Java objects, the low-level C++ API still uses Java objects for callbacks and may cause the app to crash.
-
-Agora recommends that you call `release` to release client objects and channel objects.
-
-### Avoid accessing released resources
-
-**(All SDKs)** If you have released the client object, Agora recommends that you do not access the related resources. Also, you cannot access the `RtmCallManager` object after releasing the client object.
-
-### Avoid releasing a channel object when it is in a callback of the channel object
-
-**(C++ SDK, Java SDK, Objective-C SDK)** If you call `release` to release a channel object in a callback of the channel object, the app will hang. The reason is that the callback adds a lock to the object and the `release` method requires the same lock.
-
-### Manage the lifecycle of resources
-
-**(C++ SDK)** The objects `IRtmService` and `IChannel` have corresponding listeners, namely `IRtmServiceEventHandler` and `IChannelEventHandler`, respectively. You must ensure that lifecycles of these listeners are longer than their objects.
-
-**(Objective-C SDK before v1.4.1)** The lifecycle of the `AgoraRtmChannel` object must be longer than that of the `AgoraRtmKit` object; otherwise, the `AgoraRtmKit` object may use the released `AgoraRtmChannel` object, causing your app to crash.
-
-## Avoid blocked callbacks
-
-Each callback of an client instance runs in the same thread. A callback starts to execute when the previous callback finishes executing. If the previous callback takes too much time to execute, the callback cannot execute in time, and the queue of callbacks keeps growing.
-
-- For the Native SDK, if the queue size reaches a certain limit, additional callbacks are discarded.
-
-- For the Web SDK, the queue size does not have a limit, but a long callback queue may cause performance issues.
-
-**(All SDKs)** Agora recommends that you execute callbacks in time and try to reduce the execution time of callbacks. Otherwise, the callbacks that follow may be blocked or lost.
-
-## Handle the SIGPIPE signal
-
-\*(Linux C /Linux Java SDK)\* The Linux C / Linux Java server SDK does not block the SIGPIPE signal. You need to choose whether to block the SIGPIPE signal based on your use case. Generally, you need to block this signal. Otherwise, the client process exits by default after receiving the signal.
-
-## When you are also using the
-
-### Handle the naming conflict of `AgoraRtmAreaCode`
-
-- **(C++ SDK, v1.4.2 or later )** Use the `AGORA_SDK_BOTH_RTM_AND_RTC` macro.
-
-- **(Objective-C SDK version 1.4.3 and later)** Whether your development language is Objective-C or Swift, Agora recommends that you use the `AgoraRtmKit_swift.h` file in the SDK package.
-
-### The sequence of SDK initialization and destruction
-
-You need to follow the "first in, last out" stack operation sequence when initializing and destroying Video and , that is, the SDK which is initialized later is destroyed first, for example:
-
- initialization → initialization → destruction → destruction
-or
- initialization → initialization → destruction → destruction
diff --git a/signaling/develop/call-invitation.mdx b/signaling/develop/call-invitation.mdx
deleted file mode 100644
index 2f8ab7ac8..000000000
--- a/signaling/develop/call-invitation.mdx
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: 'Call invitations'
-sidebar_position: 4
-type: docs
-description: >
- The implementation workflow for one user to invite another to join a channel.
----
-
-import CallInvitation from '@docs/shared/signaling/call-invitation/index.mdx';
-
-
-export const toc = [{}];
-
-
-The Agora supports the call invitation function, including the following behaviors in common call scenarios:
-
-- Caller: Sends or cancels a call invitation.
-
-- Callee: Accepts or refuses a call invitation.
-
-![1602314541995](https://web-cdn.agora.io/docs-files/1602314541995)
-
-
-This functionality is not available for Unity.
-
-
-
-The call invitation function provided by the Agora only implements the basic control logic of the call invitation: sending, canceling, accepting, and refusing the call invitation. The Agora does not handle operations after a callee accepts the invitation, nor does it manage the entire lifecycle. You must implement that yourself according to your requirements.
-
-The call invitation can be applied to the following scenarios:
-
-- A call invitation requiring notification of an incoming call.
-
-- A screen-sharing scenario requiring call invitations.
-
-- A whiteboard-sharing scenario requiring a video call between two parties.
-
-- A call invitation requiring synchronizing the states of both parties.
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/signaling/develop/call-invite-notification.mdx b/signaling/develop/call-invite-notification.mdx
deleted file mode 100644
index d8a23e0f3..000000000
--- a/signaling/develop/call-invite-notification.mdx
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: 'Call notifications'
-sidebar_position: 7
-type: docs
-description: >
- Implement call notification in your app.
----
-
-export const toc = [{}];
-
-To implement call notification, you need to integrate the Agora , the Agora , and platform-specific call APIs such as ConnectionService for Android, CallKit for iOS, and CallKeep for Flutter and React Native. The supports call notification only when the app is running. So, you also need to integrate platform-specific APIs to ensure that users can still receive call notifications when the app is on the background or the process is closed.
-
-## Implementation
-
-### Step 1: Integrate the and the
-
-Refer to the following articles to learn how to integrate the VSDK and the :
-
-- [ quickstart](/interactive-live-streaming/get-started/get-started-sdk)
-- [ quickstart](/video-calling/get-started/get-started-sdk)
-- [ quickstart](/signaling/get-started/get-started-sdk)
-
-### Step 2: Use the to implement the basic functionalities of call invitation
-
-To implement call invitation for the , see
-[Call Invitation](call-invitation).
-
-### Step 3: Integrate platform-specific call APIs and implement call notification
-
-- For the Android platform, see [ConnectionService official documentation](https://developer.android.com/reference/android/telecom/ConnectionService).
-
-- For the iOS platform, see [CallKit official documentation](https://developer.apple.com/documentation/callkit).
-
-- For the Flutter platform and the React Native platform, refer to the official documentation of [Flutter](https://pub.dev/packages/callkeep) and [React Native](https://github.com/react-native-webrtc/react-native-callkeep).
diff --git a/signaling/develop/cloud-proxy.mdx b/signaling/develop/cloud-proxy.mdx
new file mode 100644
index 000000000..691bbb70c
--- /dev/null
+++ b/signaling/develop/cloud-proxy.mdx
@@ -0,0 +1,12 @@
+---
+title: 'Connect through restricted networks with Cloud Proxy'
+sidebar_position: 4
+description: >
+ Implement Agora Cloud Proxy feature for reliable audio and video connectivity.
+---
+
+import CloudProxy from '@docs/shared/signaling/cloud-proxy/_cloud-proxy.mdx';
+
+export const toc = [{}];
+
+
diff --git a/signaling/develop/data-encryption.mdx b/signaling/develop/data-encryption.mdx
new file mode 100644
index 000000000..071549d40
--- /dev/null
+++ b/signaling/develop/data-encryption.mdx
@@ -0,0 +1,13 @@
+---
+title: 'Data encryption'
+sidebar_position: 4
+type: docs
+description: >
+ Add Agora built-in encryption method to your app.
+---
+
+import MediaEncryption from '@docs/shared/signaling/data-encryption/_data_encryption.mdx';
+
+export const toc = [{}];
+
+
\ No newline at end of file
diff --git a/signaling/develop/geofencing.mdx b/signaling/develop/geofencing.mdx
deleted file mode 100644
index 801941739..000000000
--- a/signaling/develop/geofencing.mdx
+++ /dev/null
@@ -1,118 +0,0 @@
----
-title: 'Geofencing'
-sidebar_position: 5
-type: docs
-description: >
- Limit Signaling functionality to different geographical areas.
----
-import Code from '@docs/shared/signaling/geofencing/sample-code/index.mdx';
-import Reference from '@docs/shared/signaling/geofencing/reference/index.mdx';
-
-export const toc = [{}];
-
-
-To meet the laws and regulations of different countries or regions, the Agora supports geofencing. You can limit the data transmission of the to a specific region. After enabling geofencing, the only connects to Agora within the specified region.
-
-This functionality is not available for Unity.
-
-
-
-
-## Implementation
-
-You need to call `AgoraRTM.setArea` method to specify the region for connection. After specifying the region, the SDK connects to the Agora servers within that region. The following regions are supported:
-
-- `GLOBAL`: (Default) Global.
-
-- `CHINA`: Mainland China.
-
-- `ASIA`: Asia excluding mainland China.
-
-- `EUROPE`: Europe.
-
-- `INDIA`: India.
-
-- `JAPAN`: Japan.
-
-- `NORTH_AMERICA`: North America.
-
-If you specify the region for connection as `GLOBAL`, you can use the `excludeArea` parameter in the `AgoraRTM.setArea` method to remove individual region from the regions for connection possibilities.
-
-**Sample code**
-
-
-
-## Considerations
-
-### Firewall requirements
-
-If a firewall is deployed in your network environment, ensure that you add the domains in the following table according to the region you specify, allow all IP addresses, and open the following firewall ports.
-
-- Whitelist domains
-
-
-
-
-
Region
-
Domain
-
-
-
-
-
Mainland China
-
webrtc2-ap-web-2.agoraio.cn
-
webrtc2-ap-web-4.agoraio.cn
-
statscollector-3.agoraio.cn
-
statscollector-4.agoraio.cn
-
logservice-china.agora.io
-
-
-
North America
-
ap-web-1-north-america.agora.io
-
ap-web-2-north-america.agora.io
-
statscollector-1-north-america.agora.io
-
statscollector-2-north-america.agora.io
-
logservice-north-america.agora.io
-
-
-
Europe
-
ap-web-1-europe.agora.io
-
ap-web-2-europe.agora.io
-
statscollector-1-europe.agora.io
-
statscollector-2-europe.agora.io
-
logservice-europe.agora.io
-
-
-
Japan
-
ap-web-1-japan.agora.io
-
ap-web-2-japan.agora.io
-
statscollector-1-japan.agora.io
-
statscollector-2-japan.agora.io
-
logservice-japan.agora.io
-
-
-
India
-
ap-web-1-india.agora.io
-
ap-web-2-india.agora.io
-
statscollector-1-india.agora.io
-
statscollector-2-india.agora.io
-
logservice-india.agora.io
-
-
-
Asia excluding mainland China
-
ap-web-1-asia.agora.io
-
ap-web-2-asia.agora.io
-
statscollector-1-asia.agora.io
-
statscollector-2-asia.agora.io
-
logservice-asia.agora.io
-
-
-
-
-- Port - See the [Firewall Requirements](../reference/firewall)
-
-## API reference
-
-
-
-
\ No newline at end of file
diff --git a/signaling/develop/integrate-token-generation.mdx b/signaling/develop/integrate-token-generation.mdx
new file mode 100644
index 000000000..f3cd123dc
--- /dev/null
+++ b/signaling/develop/integrate-token-generation.mdx
@@ -0,0 +1,13 @@
+---
+title: 'Token generators'
+sidebar_position: 15
+type: docs
+description: >
+ Integrate token generation libraries into your authentication server.
+---
+
+import IntegrateToken from '@docs/shared/video-sdk/develop/_integrate-token-generation.mdx';
+
+export const toc = [{}];
+
+
diff --git a/signaling/develop/manage-connection-states.mdx b/signaling/develop/manage-connection-states.mdx
deleted file mode 100644
index 3328e1688..000000000
--- a/signaling/develop/manage-connection-states.mdx
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: 'Manage connection states'
-sidebar_position: 2
-type: docs
-description: >
- Handle network activity when your users login and logout of Agora.
----
-import ConnectionStates from '@docs/shared/signaling/manage-connection-states/index.mdx';
-
-
-export const toc = [{}];
-
-When users log in and out of , or when the network connection state changes, the connection between the Agora and Agora switches between different states. The possible connection states are as follows:
-
-- CONNECTION_STATE_DISCONNECTED - The user is not connected.
-
-- CONNECTION_STATE_CONNECTING - The user is connecting.
-
-- CONNECTION_STATE_CONNECTED - The user is connected.
-
-- CONNECTION_STATE_RECONNECTING - The user is reconnecting.
-
-- CONNECTION_STATE_ABORTED - The user is kicked out.
-
-In the following figure, the solid lines show conditions where the SDK automatically switches states, and the dotted lines show conditions where the user needs to actively call APIs to switch states.
-
-Whenever the connection state changes, the returns the latest state (the `ConnectionState` enumeration) and the cause for the state change (the `ConnectionStateReason` enumeration) through the `onConnectionStateChanged` callback. You can manage connection states through this callback.
-
-![1611310537584](https://web-cdn.agora.io/docs-files/1611310537584)
-
-
\ No newline at end of file
diff --git a/signaling/develop/presence.mdx b/signaling/develop/presence.mdx
new file mode 100644
index 000000000..72d730e53
--- /dev/null
+++ b/signaling/develop/presence.mdx
@@ -0,0 +1,14 @@
+---
+title: 'Presence'
+sidebar_position: 2
+type: docs
+description: >
+ Manage user presence and their status in a channel.
+---
+
+import Presence from '@docs/shared/signaling/presence/_presence.mdx';
+
+export const toc = [{}];
+
+
+
diff --git a/signaling/develop/storage.mdx b/signaling/develop/storage.mdx
new file mode 100644
index 000000000..8da90bc52
--- /dev/null
+++ b/signaling/develop/storage.mdx
@@ -0,0 +1,14 @@
+---
+title: 'Store channel and user data'
+sidebar_position: 3
+type: docs
+description: >
+ Use metadata to enhance user and channel features in Signaling clients.
+---
+
+import Storage from '@docs/shared/signaling/storage/_storage.mdx';
+
+export const toc = [{}];
+
+
+
diff --git a/signaling/enable-features/_category_.json b/signaling/enable-features/_category_.json
new file mode 100644
index 000000000..896ecd214
--- /dev/null
+++ b/signaling/enable-features/_category_.json
@@ -0,0 +1,6 @@
+{
+ "position": 4,
+ "label": "Integrate features",
+ "collapsible": true,
+ "link": null
+}
diff --git a/signaling/enable-features/geofencing.mdx b/signaling/enable-features/geofencing.mdx
new file mode 100644
index 000000000..00f334022
--- /dev/null
+++ b/signaling/enable-features/geofencing.mdx
@@ -0,0 +1,13 @@
+---
+title: 'Geofencing'
+sidebar_position: 5
+type: docs
+description: >
+ Limit Signaling functionality to different geographical areas.
+---
+
+import Geofencing from '@docs/shared/signaling/geofencing/index.mdx';
+
+export const toc = [{}];
+
+
diff --git a/signaling/get-started/authentication-workflow.mdx b/signaling/get-started/authentication-workflow.mdx
new file mode 100644
index 000000000..25a675267
--- /dev/null
+++ b/signaling/get-started/authentication-workflow.mdx
@@ -0,0 +1,14 @@
+---
+title: 'Secure authentication with tokens'
+sidebar_position: 3
+type: docs
+description: >
+ Retrieve tokens generated by an authentication token server to securely connect to Signaling.
+---
+
+import AuthenticationWorkflow from '@docs/shared/signaling/authentication-workflow/index.mdx';
+
+export const toc = [{}];
+
+
+
diff --git a/signaling/get-started/get-started-sdk.mdx b/signaling/get-started/get-started-sdk.mdx
index 972c8b603..b1d5ca24f 100644
--- a/signaling/get-started/get-started-sdk.mdx
+++ b/signaling/get-started/get-started-sdk.mdx
@@ -1,69 +1,12 @@
---
-title: 'SDK quickstart'
+title: 'Message channel quickstart'
sidebar_position: 1
description: >
- Rapidly develop and easily enhance your social, work, education and IoT apps.
+ Rapidly develop and easily enhance your social, work, educational, and IoT apps.
---
-import Prerequites from '@docs/shared/common/prerequities.mdx';
import GetStartedSDK from '@docs/shared/signaling/get-started-sdk/index.mdx';
export const toc = [{}];
-This page shows you how to create your first using .
-
-## Understand the tech
-
-#### Log in to
-
-![](/images/signaling/login-to-signaling.png)
-
-The login process includes:
-
-1. The app client requests a token from your app server.
-
-2. The app server returns the token to the app client.
-
-3. The app client logs in to with the token.
-
-#### Peer-to-peer messaging
-
-![](/images/signaling/peer-to-peer-messaging.png)
-
-The peer messaging process includes:
-
-1. Client A sends a peer message to .
-
-2. sends the message to client B. Client B receives the peer message.
-
-#### Channel messaging
-
-![](/images/signaling/channel-messaging.png)
-
-The channel messaging process includes:
-
-1. Client A creates a channel and join the channel.
-
-2. Client B and client C joins the channel created by client A.
-
-3. Client A sends a channel message to .
-
-4. sends the channel message to client B and C. Client B and client C receives the message.
-
-For an app client to join a channel, you need the following information:
-
-- The App ID: A randomly generated string provided by Agora for identifying your app. You can get the App ID from .
-
-- The user ID: The unique identifier of a user. You need to specify the user ID yourself, and ensure that it is unique in the channel.
-
-- A token: In a test or production environment, your app client retrieves tokens from your server.
-
-- The channel name: A string that identifies the channel for the channel messaging.
-
-## Prerequisites
-
-In order to follow this procedure you must have:
-
-
-
-
+
\ No newline at end of file
diff --git a/signaling/get-started/run-the-sample-project.mdx b/signaling/get-started/run-the-sample-project.mdx
deleted file mode 100644
index 85c195754..000000000
--- a/signaling/get-started/run-the-sample-project.mdx
+++ /dev/null
@@ -1,46 +0,0 @@
----
-title: 'Run the sample project'
-sidebar_position: 2
-type: docs
-description: >
- Install and run the Signaling example project.
----
-
-import Prerequites from '@docs/shared/common/prerequities.mdx';
-import RunSampleProject from '@docs/shared/signaling/run-the-sample-project/index.mdx';
-
-export const toc = [{}];
-
-This page shows you how to install and run the example project.
-
-## Prerequisites
-
-In order to follow this procedure you must have:
-
-
-
-## Implement Signaling
-
-### 1. Create an Agora project
-
-Create a project in , as follows:
-
-1. Log in to Console, and click **Project Management** in the left navigation menu to enter the [Project Management](https://dashboard.agora.io/projects) page.
-
-2. Click **Create**.
-
- ![create button](https://web-cdn.agora.io/docs-files/1594949127367)
-
-3. Enter your project name, and select **APP ID** for the authentication mechanism in the pop-up window. Agora recommends using an App ID for authentication only in a test environment, or if your project has low security requirements.
-
-4. Click **Submit**. You can see the created project on the **Project Management** page.
-
-### 2. Get an App ID
-
-Agora automatically assigns each project an App ID as a unique identifier.
-
-To copy this App ID, find your project on the [Project Management](https://dashboard.agora.io/projects) page in Agora Console, and click the eye icon to the right of the App ID.
-
-![get app id](https://web-cdn.agora.io/docs-files/1602646621028)
-
-
diff --git a/signaling/get-started/stream-channel.mdx b/signaling/get-started/stream-channel.mdx
new file mode 100644
index 000000000..898337cb4
--- /dev/null
+++ b/signaling/get-started/stream-channel.mdx
@@ -0,0 +1,13 @@
+---
+title: 'Stream channel quickstart'
+sidebar_position: 2
+type: docs
+description: >
+ Stream messages to and from a room.
+---
+
+import StreamChannel from '@docs/shared/signaling/stream-channel/_stream-channel.mdx';
+
+export const toc = [{}];
+
+
\ No newline at end of file
diff --git a/signaling/overview/pricing.mdx b/signaling/overview/pricing.mdx
index cb858b01b..9e45268b4 100644
--- a/signaling/overview/pricing.mdx
+++ b/signaling/overview/pricing.mdx
@@ -5,53 +5,184 @@ description: >
Provides you with information on billing, fee deductions, free-of-charge policy, and any suspension to your account based on the account type.
---
+import Pricing from '@docs/shared/signaling/reference/_pricing.mdx'
+
export const toc = [{}]
This page explains how calculates your monthly bill for .
+
+
If you have already signed a contract with , the billing terms and conditions within that contract take precedence.
-## pricing
+## 1.x pricing plans, features, and limitations
+
+ provides the following pricing plans:
+
+- [Free](#free-pricing)
+- [Self-service](#self-service-pricing)
+- [Enterprise](#enterprise-pricing)
+
+The available features and monthly fees depend on the plan you select. You can check and export the usage status as well as upgrade or downgrade your pricing plan at any time in . Plan upgrade takes effect immediately, while plan downgrade takes effect the following month.
+
+### Free pricing
+
+The Free pricing plan allows you to experience the core features with the following limitations:
+
+|Plan|Limitations|Monthly fee|
+|:---|:----------|:----------|
+|Free|
3,000,000 messages/month
100 PCU (peak concurrent users)/month
1 GB storage capacity/month
|Free|
+
+If your usage exceeds the above limitations, you receive an error code and an email reminder. You cannot continue using until the end of the corresponding month.
+
+### Self-service pricing
+
+The Self-service pricing plan includes two tiers with the following limitations:
+
+|Plan|Limitations|Monthly fee|
+|:---|:----------|:----------|
+|Self-service startup|
30,000,000 messages/month
500 PCU/month
2 GB free storage capacity/month
|399 RMB|
+|Self-service business|
600,000,000 messages/month
10,000 PCU/month
10 GB storage capacity/month
|5499 RMB|
+
+If your usage exceeds the above limitations, you receive an error code and an email reminder. You can continue using , and your usage
+above the limitations is billed according to the Enterprise pricing.
+
+### Enterprise pricing
+
+The Enterprise pricing plan does not set any additional limitations, besides [the API usage restrictions](limitations), and includes per-usage billing.
+
+The monthly fee is calculated using the following basic formula:
+
+**Monthly fee** = **PCU fee** + **message number fee** + **storage capacity fee**
The unit prices are as follows:
-| Highest number of DAUs for the month | Pricing, US$/1,000 DAUs |
-|--------------------------------------|------------------------|
-| ≤ 1,000 | 0 |
-| > 1,000 | 14.50 |
+|Item|Pricing|
+|:---|:------|
+|PCU|100 RMB/1,000 peak connections|
+|Price per message|10 RMB/1,000,000 messages|
+|Storage unit price|50 RMB/1 GB|
+
+See [How Agora measures your usage](#how-agora-measures-your-usage) to understand how PCU, number of messages, and storage consumption are calculated.
+
+### Pricing plan feature comparison
+
+The following features and support are available based on the pricing plan you select:
+
+|Feature |Free |Self-service |Enterprise |
+|:------------------|:------|:--------------|:----------|
+|Publish/subscribe to messages |✔ |✔| ✔|
+|Stream channel |✔ |✔ |✔|
+|Presence |✔ |✔ |✔|
+|Storage |✔ |✔ |✔|
+|Historical messages |✔ |✔ |✔|
+|Lock |✔ |✔ |✔|
+|Authentication |✔ |✔ |✔|
+|Console |✔ |✔ |✔|
+|Webhook |✔ |✔| ✔|
+|Analytics |✔ |✔| ✔|
+|Uptime SLA |✘ |✔ |✔|
+|Geofencing |✘ |✘ |✔|
+|GDPR & HIPAA compliance guarantee |✘ |✘ |✔|
+|Customized requirements |✘ |✘ |✔|
+|Private deployment |✘ |✘ |✔|
+|Service hours |12 x 7 |24 x 7 |24 x 7|
+|Support reply speed |As soon as possible |24 hours| 1 hour|
+|Email support |✔ |✔ |✔|
+|Enterprise WeChat support |✔ |✔ |✔|
+|Phone support |✔ |✔ |✔|
+|Slack support |✘ |✔ |✔|
+|Technical expert support |✘ |✘| ✔|
+|Dedicated SA support |✘ |✘ |✔|
+|Usage discount |✘ |✘ |✔|
+
+
+## How Agora measures your usage
+
+Agora measures your PCU, number of messages, and storage consumption in the following way:
+
+#### PCU
-## Cost calculation
+Your PCU is the maximum number of real-time users simultaneously connected to the server at any point in a calendar month. For example, if you have 10,000 users and a maximum of 500 users simultaneously connect in a given month, your PCU value for that month is 500, which means you only pay for 500 peak connections. The total number of users or devices that connected the server in that month does not affect the fee.
-Billing for begins once you implement the signaling
-functionality using and occurs monthly.
+#### Total number of messages
- calculates your monthly pricing by adding up the highest number of
-**daily active users (DAUs)** for the month of each project in your
- developer account, subtracting the 1,000 free DAUs, and multiplying by unit pricing.
+The size of a single message in is calculated as 1 KB. If you send a 10 KB message to a channel or topic subscribed to by 100 people, it counts as 10 inbound messages and 1,000 outbound messages, totalling in 1,010 messages. The following additional rules apply for calculating the number of messages:
-The basic formula is shown here:
+##### Sending and receiving messages
-**Monthly cost** = (**total DAUs in all projects** - **1000 free DAUs**) / 1,000 × **unit pricing**
+ Every published or received message in a message channel counts as 1 message. For example, if a user sends 1 message to a message channel, and the channel is subscribed to by 10 other users, it counts as 1 sent message and 10 received messages, totaling in 11 messages.
-## Examples
+ Every published or received message in a topic in a stream channel counts as 1 message. For example, if a users sends 1 message to a topic in a stream channel, and the topic is subscribed to by 10 other users, it counts as 1 sent message and 10 received messages, totaling in 11 messages.
-This section illustrates how calculates the cost for .
+ Note that even if the user enables message filtering, it does not change how the number of messages is calculated. The client-side message filtering is only for the convenience of developers, since a filtered message is still delivered. The asynchronous callback generated by publishing a message in a message channel or a stream channel does not count as a message.
-### One project
+ Messages sent using the RESTful API also count as messages. If a user does not subscribe to the channel or topic, they do not receive any messages.
-Suppose you are using and the highest number of DAUs for the month is 9,000.
-Because the first 1,000 in the highest number of DAUs for the month are free of charge,
-the number of DAUs used to calculate the monthly billing is 8,000.
-Therefore, the monthly billing for is 8,000 (DAUs) / 1,000 × $14.50 = $116.
+##### Presence
-### Two projects
+ Every published or received event notification about a user's presence, for example, about a user entering or leaving a channel, counts as one message. For example, if a user enters a channel subscribed to by 10 other users that have enabled the `withPresence` parameter to receive presence notifications, then 1 presence notification is sent and 10 notifications are received, totalling in 11 messages.
-Suppose you are using in project A and project B. The highest number of DAUs for the
-month in project A is 9,000, and the highest number of DAUs for the month in project B is 5,000.
-Because the first 1,000 in the highest number of DAUs for the month are free of charge,
-the number of DAUs used to calculate the monthly billing is 9,000 + 5,000 - 1,000 = 13,000.
-Therefore, the monthly billing for is 13,000 (DAUs) / 1,000 × $14.50 = $188.5.
+ User message filtering has no effect on the presence event notification count.
+
+ If the user does not want to send or receive presence notification events when joining or subscribing to a channel, they can set the `beQuite` parameter to `true` and `withPresence` to `false`. In this case, they won't receive any presence notifications. This affects the total count of presence event notifications.
+
+##### Storage
+
+ Setting, querying, updating, and deleting any item of the channel metadata in a message channel or stream channel counts as one message. Receiving an event notification about a change in a message or stream channel metadata counts as 1 message. For example, if a user sets a metadata item in a channel subscribed to by 10 other users who have enabled the `withMetadata` parameter to receive metadata change notifications, then 1 notification is sent and 10 notifications are received, totalling in 11 messages.
+
+ Setting, querying, updating, and deleting any item of the user metadata counts as one message. Receiving an event notification about a change in a user metadata counts as 1 message. For example, if a user sets an item in a user metadata subscribed to by 10 other users, then 1 notification is sent and 10 notifications are received, totalling in 11 messages.
+
+ User message filtering has no effect on metadata change notification counts.
+
+ If a user does not want to receive metadata change notifications when joining or subscribing to a channel, they can set `withMetaData` to `false`. If a user does not join a stream channel or subscribe to a message channel, they don't receive channel metadata change notifications. If a user does not subscribe to other users' metadata, they do not receive other user's metadata change notifications.
+
+##### Lock
+
+ Setting, querying, releasing, depriving, and deleting a lock counts as one message. Receiving an event notification about a lock change in a message or stream channel counts as 1 message. For example, if a user sets a lock on a channel subscribed to by 10 other users who have enabled the `withLock` parameter to receive lock change notifications, then 1 notification is sent and 10 notifications are received, totalling in 11 messages.
+
+#### Storage capacity
+
+Storage in generates cloud storage occupancy. Agora samples and accumulates your actual cloud storage occupancy at 1-hour intervals to calculate the monthly fee.
+
+
+
+
+
+
+
+## 2.x pricing plans
+
+ 2.x is an enhanced version with wide range of new features compared to 1.x and follows a new pricing structure. You can choose from the following packages:
+
+| Signaling 2.x Package | Description | Package Price Per Month in USD |
+|:-------------|:------------------------------------------|:----------|
+| Free Package | Up to 100 Peak Concurrent Users, 100K Daily Message Volume, 3M Monthly Message volume, 1 GB Storage | Free |
+| Starter Package | Up to 500 Peak Concurrent Users, 1M Daily Message Volume, 30M Monthly Message volume, 2 GB Storage | $59.00 |
+| Pro Package | Up to 5K Peak Concurrent Users, 10M Daily Message Volume, 300M Monthly Message volume, 5 GB Storage | $399.00 |
+| Business Package | Up to 10K Peak Concurrent Users, 20M Daily Message Volume, 600M Monthly Message volume, 10 GB Storage | $820.00 |
+| Enterprise Package | For Every 1000 Peak Concurrent Users - $15 For Every 1 Million Message Count - $1.5 For Every GB of Storage -$7.5 | Pay –as-you-go (with defined unit prices in the description ) |
+
+### Costing examples
+
+**Example 1 - Fixed package**: Suppose you are using Signaling 2.x and the number of peak concurrent users (PCU) is less than 5,000, the total message count per month is less than 10 million and storage usage is less than 5 GB. The monthly billing will be $399 considering the Pro Package.
+
+**Example 2 - Pay-as-you-go**: Suppose you are using Signaling 2.x and the number of peak concurrent users is 20,000, the total message count per month is 7051 Million and the storage is 10 GB. The monthly billing will be as follows:
+
+* PCU: 20,000
+ * PCU Cost = 20000/1000 x $15 = $300 ($15 for Every 1000 Peak Concurrent Users)
+* Message count: 7051 Million
+ * Messages Cost = 7051 x $1.50 = $10,576 ($1.50 for every 1 million message count)
+* Storage: 10GB
+ * Storage cost = 10 x $7.50= $75 ($7.50 for Every GB of Storage -$7.5)
+* **Total cost**: $300 + $10,576 + $75 = $10,951 USD (PCU + Messages + Storage)
+
+
## See also
[Billing policies and free-of-charge policy](../reference/billing-policies)
+
+
+
+
diff --git a/signaling/overview/product-overview.mdx b/signaling/overview/product-overview.mdx
index c7b830c96..1128a5ee0 100644
--- a/signaling/overview/product-overview.mdx
+++ b/signaling/overview/product-overview.mdx
@@ -8,42 +8,54 @@ description: >
- Information about changes in each release of Video Calling.
+ Information about changes in each release of Signaling.
---
@@ -11,15 +11,9 @@ import ReleaseNotes from '@docs/shared/signaling/release-notes/index.mdx';
export const toc = [{}];
-The following features are deprecated starting from v1.5.0:
-
-- Send and receive image or file messages
-- Historical messages
-- Offline messages
+The Agora provides a streamlined and stable messaging mechanism for you to quickly implement real-time messaging for various scenarios. See [product overview](../overview/product-overview) for more information.
-If you have integrated those features in an earlier release and want to continue using them, you can do so in v1.4.x.
-The Agora provides a streamlined and stable messaging mechanism for you to quickly implement real-time messaging for various scenarios. See [product overview](../overview/product-overview) for more information.
diff --git a/signaling/overview/supported-platforms.mdx b/signaling/overview/supported-platforms.mdx
index 07bc81184..c19e2e521 100644
--- a/signaling/overview/supported-platforms.mdx
+++ b/signaling/overview/supported-platforms.mdx
@@ -3,7 +3,7 @@ title: 'Supported platforms'
sidebar_position: 5
type: docs
description: >
- A list of terms used in Agora documentation.
+ A list of platforms supported by Signaling SDK.
---
import SupportedPlatform from '@docs/shared/common/_supported-platforms.mdx';
diff --git a/signaling/reference/_category_.json b/signaling/reference/_category_.json
index d540fd6db..76945a7f9 100644
--- a/signaling/reference/_category_.json
+++ b/signaling/reference/_category_.json
@@ -1,5 +1,5 @@
{
- "position": 4,
+ "position": 5,
"label": "Reference",
"collapsible": true,
"link": null
diff --git a/signaling/reference/api.mdx b/signaling/reference/api.mdx
index 283a76100..c2f267aa3 100644
--- a/signaling/reference/api.mdx
+++ b/signaling/reference/api.mdx
@@ -1,20 +1,21 @@
---
-title: 'API reference'
-sidebar_position: 6
+title: 'Signaling SDK API reference'
+sidebar_position: 3
type: docs
description: >
Links to the API reference for your platform
---
+import WebAPI from '@docs/shared/signaling/reference/api-ref/index.mdx';
-You find the API reference documentation at:
+export const toc = [{}];
-
-- Web API reference
-- [User and channel events REST API](user-channel-events)
-- [Messaging REST API](restful-messaging)
+
+You find the API reference documentation at:
+
+
- Android API reference
- [User and channel events REST API](user-channel-events)
diff --git a/signaling/reference/cloud-proxy-allowed-ips.mdx b/signaling/reference/cloud-proxy-allowed-ips.mdx
new file mode 100644
index 000000000..2ce375a2f
--- /dev/null
+++ b/signaling/reference/cloud-proxy-allowed-ips.mdx
@@ -0,0 +1,16 @@
+---
+title: "IP addresses for Cloud Proxy"
+weight: 12
+type: docs
+description: >
+ If your app is running inside a restricted network, clear one of the following IP addresses for Cloud Proxy to work effectively.
+
+---
+
+import Proxy from '../../shared/common/_cloud-proxy-allowed-iplist.mdx';
+
+export const toc = [{}];
+
+
+
+
diff --git a/signaling/reference/downloads.mdx b/signaling/reference/downloads.mdx
index e2329aeeb..d013dd845 100644
--- a/signaling/reference/downloads.mdx
+++ b/signaling/reference/downloads.mdx
@@ -1,6 +1,6 @@
---
title: 'Manual install'
-sidebar_position: 3
+sidebar_position: 5
type: docs
description: >
Links to the manual downloads for this product, and explanations on how to install them.
@@ -23,38 +23,62 @@ To manually install , do one of the following:
```javascript
```
-1. (Optional) To enable smart completions and type checking, take the following steps:
+1. To enable smart completions and type checking, take the following steps:
-1. Go to `libs/agora-rtm-sdk.d.ts` in the SDK folder, and save the TS file to your project directory.
+ 1. Go to `libs/agora-rtm-sdk.d.ts` in the SDK folder, and save the TS file to your project directory.
-1. Add the following line to the beginning of the JS or TS file (you should replace path to the TS file with the path to agora-rtm-sdk.d.ts).
+ 1. Add the following line to the beginning of the JS or TS file (you should replace path to the TS file with the path to agora-rtm-sdk.d.ts).
- ```javascript
- ///
- ```
+ ```javascript
+ ///
+ ```
## Through npm
-1. In `package.json`, add agora-rtm-sdk and its version number to the dependencies field:
+1. In `package.json` for your project, add `agora-rtm-sdk` and its version number to the dependencies field:
+ ```json
+ "dependencies": {
+ "agora-rtm-sdk": "^2.1.4-beta.0",
+ "cors-anywhere": "^0.4.4",
+ "livereload-js": "^4.0.1",
+ "url": "^0.11.1"
+ },
+ ```
+
+1. Install the dependencies
+ ```bash
+ pnpm install
+ ```
+
+1. In your code, import `RTM` from `agora-rtm-sdk`.
```javascript
- {
- "name": "web",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "test": "echo "Error: no test specified" && exit 1"
- },
- "dependencies": {
- "agora-rtm-sdk": "latest"
- },
- "author": "",
- "license": "ISC"
- }
+ // Import the SDK
+ import AgoraRTM from 'agora-rtm-sdk';
+
+ // Create a client instance
+ const signalingEngine = new AgoraRTM.RTM('app-id', 'user-id', { token: 'temporary-token' });
+
+ // Listen for events
+ signalingEngine.addEventListener('message', (eventArgs) => {
+ console.log(`${eventArgs.publisher}: ${eventArgs.message}`);
+ })
+
+ // Login
+ try {
+ await signalingEngine.login();
+ } catch (err) {
+ console.log({ err }, 'error occurs at login.');
+ }
+
+ // Send channel message
+ try {
+ await signalingEngine.publish('channel', 'hello world');
+ } catch (err) {
+ console.log({ err }, 'error occurs at publish message');
+ }
```
-1. Import the AgoraRTM module in the JS or TS file:
-1. import AgoraRTM from 'agora-rtm-sdk'
+
diff --git a/signaling/reference/limitations.mdx b/signaling/reference/limitations.mdx
index de42a19b6..689fd739f 100644
--- a/signaling/reference/limitations.mdx
+++ b/signaling/reference/limitations.mdx
@@ -1,15 +1,19 @@
---
-title: 'Limitations'
-sidebar_position: 2
+title: 'API usage restrictions'
+sidebar_position: 4
description: >
- A brief overview of the limitations of the Signaling SDK, including API call limit, string size, encoding, and more.
+ A brief overview of the restrictions of the Signaling SDK, including API call limit, string size, encoding, and more.
---
import Limitations from '@docs/shared/signaling/limitations/index.mdx';
export const toc = [{}];
-This page provides a brief overview of the limitations of the Agora .
+This page provides a brief overview of the restrictions of the Agora , including API call limit, string size, encoding, and more.
+
+This page provides a brief overview of the restrictions of the Agora , including API call limit, string size, encoding, and more.
+
+
## General
@@ -24,24 +28,6 @@ The following general limits apply:
To extend any of the above limits, contact support@agora.io.
-## Channel message limit
-
-Agora has the following recommendations on the maximum number of channel messages per second for a single channel:
-
-| Concurrent online users in a single channel | Number of channel messages per second |
-| ------------------------------------------- | ----------------------------- |
-| < 1,000 | < 200 |
-| ≥ 1,000 and < 10,000 | < 100 |
-| ≥ 10,000 | < 30 |
-
-If the number of messages per second exceeds the recommended values, latency can increase significantly and may also cause the following issues:
- - The user cannot send or receive messages.
- - The user always stays in the `RECONNECTING` state or keeps switching between the `CONNECTED` state and the `RECONNECTING` state. For other users, the current user may appear offline.
-
-Agora provides customized service to increase the number of messages per second without affecting latency or stability. Please contact support@agora.io for more information.
-
-
-
## Miscellaneous
- Notifications of a member joining or leaving the channel are automatically disabled when the number of channel members exceeds 512.
diff --git a/signaling/reference/manage-agora-account.mdx b/signaling/reference/manage-agora-account.mdx
index f02ac9b16..198bfe51e 100644
--- a/signaling/reference/manage-agora-account.mdx
+++ b/signaling/reference/manage-agora-account.mdx
@@ -1,6 +1,6 @@
---
title: 'Agora account management'
-sidebar_position: 4
+sidebar_position: 7
type: docs
description: >
Create, manage and update your Agora account.
diff --git a/signaling/reference/user-channel-events.mdx b/signaling/reference/user-channel-events.mdx
index e140cc84d..59897ade2 100644
--- a/signaling/reference/user-channel-events.mdx
+++ b/signaling/reference/user-channel-events.mdx
@@ -1,6 +1,6 @@
---
title: 'User and channel events REST API'
-sidebar_position: 1
+sidebar_position: 6
type: docs
description: >
Enable UIKit to connect to a token server and securely connect to Agora channels.
diff --git a/video-calling/reference/cloud-proxy-allowed-ips.mdx b/video-calling/reference/cloud-proxy-allowed-ips.mdx
index 5fde83e45..2ce375a2f 100644
--- a/video-calling/reference/cloud-proxy-allowed-ips.mdx
+++ b/video-calling/reference/cloud-proxy-allowed-ips.mdx
@@ -7,7 +7,7 @@ description: >
---
-import Proxy from '../../shared/video-sdk/_cloud-proxy-allowed-iplist.mdx';
+import Proxy from '../../shared/common/_cloud-proxy-allowed-iplist.mdx';
export const toc = [{}];
diff --git a/voice-calling/reference/cloud-proxy-allowed-ips.mdx b/voice-calling/reference/cloud-proxy-allowed-ips.mdx
index 5fde83e45..2ce375a2f 100644
--- a/voice-calling/reference/cloud-proxy-allowed-ips.mdx
+++ b/voice-calling/reference/cloud-proxy-allowed-ips.mdx
@@ -7,7 +7,7 @@ description: >
---
-import Proxy from '../../shared/video-sdk/_cloud-proxy-allowed-iplist.mdx';
+import Proxy from '../../shared/common/_cloud-proxy-allowed-iplist.mdx';
export const toc = [{}];