diff --git a/.github/workflows/build-and-lint.yml b/.github/workflows/build-and-lint.yml
new file mode 100644
index 000000000..d92c4001a
--- /dev/null
+++ b/.github/workflows/build-and-lint.yml
@@ -0,0 +1,46 @@
+name: Build and Lint
+
+on:
+ push:
+ branches: [main, develop]
+ pull_request:
+ branches: [main, develop]
+
+jobs:
+ build-and-lint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: 16.19.0
+
+ - name: Set up Yarn
+ uses: actions/setup-node@v3
+ with:
+ node-version: 16.19.0
+ cache: 'yarn'
+
+ - name: Cache dependencies
+ uses: actions/cache@v2
+ with:
+ path: ~/.yarn
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+
+ - name: Install dependencies
+ run: yarn
+
+ - name: Format check
+ run: yarn format:check
+
+ - name: Lint check
+ run: yarn lint
+
+ - name: Build
+ run: yarn build && yarn build:storybook
diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
new file mode 100644
index 000000000..088f92dcd
--- /dev/null
+++ b/.github/workflows/playwright.yml
@@ -0,0 +1,60 @@
+name: Playwright Tests
+on:
+ push:
+ branches: [main, develop]
+ pull_request:
+ branches: [main, develop]
+jobs:
+ test:
+ timeout-minutes: 60
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Setup Node.js environment
+ uses: actions/setup-node@v3
+ with:
+ node-version: 16.19.0
+
+ - name: Cache dependencies
+ uses: actions/cache@v2
+ with:
+ path: ~/.yarn
+ key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-yarn-
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Build the project
+ run: |
+ yarn build
+
+ - name: Get installed Playwright version
+ id: playwright-version
+ run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')"
+
+ - uses: actions/cache@v3
+ id: playwright-cache
+ with:
+ path: '~/.cache/ms-playwright'
+ key: '${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}'
+ restore-keys: |
+ ${{ runner.os }}-playwright-
+
+ - name: Install Playwright's dependencies
+ if: steps.playwright-cache.outputs.cache-hit != 'true'
+ run: npx playwright install --with-deps
+
+ - name: Run Playwright tests
+ run: cd packages/e2e-react && npx playwright test
+
+ - name: Upload Playwright report
+ uses: actions/upload-artifact@v3
+ if: always()
+ with:
+ name: playwright-report
+ path: packages/e2e-react/playwright-report/
+ retention-days: 30
diff --git a/README.md b/README.md
index 81b81cd01..e5abf5c7f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,7 @@
-# EmbeddedChat
+
Embedded chat: A staple in excellent customer service
+
+![image](https://github.com/coderboy-yash/EmbeddedChat/assets/109899959/b2961a35-4300-48df-b674-8a128c73e838)
+
An easy to use full-stack component (ReactJS + backend behaviors) embedding Rocket.Chat into your webapp.
@@ -13,7 +16,7 @@ _EmbeddedChat is a full-stack React component node module of the RocketChat appl
## Installation and Usage
-Installtion and usage documentation could be found here [EmbeddedChat installation and usage](packages/react/README.md)
+Installation and usage documentation could be found here [EmbeddedChat installation and usage](packages/react/README.md)
## Development
@@ -42,6 +45,7 @@ To develop and test `EmbeddedChat`, a local instance of Rocket.Chat server is ne
Install all necessary dependencies and build the packages (`auth`, `api`, and `react`) with:
+
```bash
yarn
```
diff --git a/package.json b/package.json
index f19dbb472..086c7ff1d 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,11 @@
"scripts": {
"preinstall": "node scripts/node-check.js",
"postinstall": "yarn build",
- "build": "lerna run build"
+ "build": "lerna run build",
+ "lint": "lerna run lint",
+ "build:storybook": "lerna run build-storybook",
+ "format": "lerna run format",
+ "format:check": "lerna run format:check"
},
"devDependencies": {
"@changesets/cli": "^2.26.2",
diff --git a/packages/api/package.json b/packages/api/package.json
index 1481af2e8..f6c304e94 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -9,7 +9,9 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup -c",
- "dev": "yarn parcel playground/index.html"
+ "dev": "yarn parcel playground/index.html",
+ "format": "prettier --write 'src/'",
+ "format:check": "prettier --check 'src/'"
},
"author": "",
"license": "ISC",
diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts
index ce81a5427..6d3cc6987 100644
--- a/packages/api/src/EmbeddedChatApi.ts
+++ b/packages/api/src/EmbeddedChatApi.ts
@@ -1,6 +1,10 @@
-import { Rocketchat } from '@rocket.chat/sdk';
-import cloneArray from './cloneArray';
-import { IRocketChatAuthOptions, RocketChatAuth, ApiError } from '@embeddedchat/auth';
+import { Rocketchat } from "@rocket.chat/sdk";
+import cloneArray from "./cloneArray";
+import {
+ IRocketChatAuthOptions,
+ RocketChatAuth,
+ ApiError,
+} from "@embeddedchat/auth";
// mutliple typing status can come at the same time they should be processed in order.
let typingHandlerLock = 0;
@@ -8,19 +12,23 @@ export default class EmbeddedChatApi {
host: string;
rid: string;
rcClient: Rocketchat;
- onMessageCallbacks: ((message: any) => void)[]
- onMessageDeleteCallbacks: ((messageId: string) => void)[]
+ onMessageCallbacks: ((message: any) => void)[];
+ onMessageDeleteCallbacks: ((messageId: string) => void)[];
onTypingStatusCallbacks: ((users: string[]) => void)[];
onActionTriggeredCallbacks: ((data: any) => void)[];
onUiInteractionCallbacks: ((data: any) => void)[];
typingUsers: string[];
auth: RocketChatAuth;
- constructor(host: string, rid: string, { getToken, saveToken, deleteToken, autoLogin }: IRocketChatAuthOptions ) {
+ constructor(
+ host: string,
+ rid: string,
+ { getToken, saveToken, deleteToken, autoLogin }: IRocketChatAuthOptions
+ ) {
this.host = host;
this.rid = rid;
this.rcClient = new Rocketchat({
- protocol: 'ddp',
+ protocol: "ddp",
host: this.host,
useSsl: !/http:\/\//.test(host),
reopen: 20000,
@@ -59,13 +67,13 @@ export default class EmbeddedChatApi {
const tokens = await signIn();
let acsPayload = null;
- if (typeof acsCode === 'string') {
+ if (typeof acsCode === "string") {
acsPayload = acsCode;
}
const payload = acsCode
? JSON.stringify({
- serviceName: 'google',
+ serviceName: "google",
accessToken: tokens.access_token,
idToken: tokens.id_token,
expiresIn: 3600,
@@ -74,24 +82,24 @@ export default class EmbeddedChatApi {
},
})
: JSON.stringify({
- serviceName: 'google',
+ serviceName: "google",
accessToken: tokens.access_token,
idToken: tokens.id_token,
expiresIn: 3600,
- scope: 'profile',
+ scope: "profile",
});
try {
const req = await fetch(`${this.host}/api/v1/login`, {
- method: 'POST',
+ method: "POST",
headers: {
- 'Content-Type': 'application/json',
+ "Content-Type": "application/json",
},
body: payload,
});
const response = await req.json();
- if (response.status === 'success') {
+ if (response.status === "success") {
if (!response.data.me.username) {
await this.updateUserUsername(
response.data.userId,
@@ -101,7 +109,7 @@ export default class EmbeddedChatApi {
return { status: response.status, me: response.data.me };
}
- if (response.error === 'totp-required') {
+ if (response.error === "totp-required") {
return response;
}
} catch (err) {
@@ -126,12 +134,9 @@ export default class EmbeddedChatApi {
try {
const data = await this.auth.loginWithPassword(credentials);
if (!data.me.username) {
- await this.updateUserUsername(
- data.userId,
- data.me.name
- );
+ await this.updateUserUsername(data.userId, data.me.name);
}
- return { status: 'success', me: data.me };
+ return { status: "success", me: data.me };
} catch (error) {
if (error instanceof ApiError && error.response?.status === 401) {
const authErrorRes = await error.response.json();
@@ -165,7 +170,7 @@ export default class EmbeddedChatApi {
return;
}
const message = JSON.parse(JSON.stringify(data));
- if( message.ts?.$date) {
+ if (message.ts?.$date) {
console.log(message.ts?.$date);
message.ts = message.ts.$date;
}
@@ -175,58 +180,68 @@ export default class EmbeddedChatApi {
this.onMessageCallbacks.map((callback) => callback(message));
});
await this.rcClient.subscribe(
- 'stream-notify-room',
+ "stream-notify-room",
`${this.rid}/user-activity`
);
- await this.rcClient.onStreamData('stream-notify-room', (ddpMessage: any) => {
- const [roomId, event] = ddpMessage.fields.eventName.split('/');
+ await this.rcClient.onStreamData(
+ "stream-notify-room",
+ (ddpMessage: any) => {
+ const [roomId, event] = ddpMessage.fields.eventName.split("/");
- if (roomId !== this.rid) {
- return;
- }
+ if (roomId !== this.rid) {
+ return;
+ }
- if (event === 'user-activity') {
- const typingUser = ddpMessage.fields.args[0];
- const isTyping = ddpMessage.fields.args[1]?.includes('user-typing');
- this.handleTypingEvent({ typingUser, isTyping });
- }
+ if (event === "user-activity") {
+ const typingUser = ddpMessage.fields.args[0];
+ const isTyping = ddpMessage.fields.args[1]?.includes("user-typing");
+ this.handleTypingEvent({ typingUser, isTyping });
+ }
- if (event === 'typing') {
- const typingUser = ddpMessage.fields.args[0];
- const isTyping = ddpMessage.fields.args[1];
- this.handleTypingEvent({ typingUser, isTyping });
- }
- if (event === 'deleteMessage') {
- const messageId = ddpMessage.fields.args[0]?._id;
- this.onMessageDeleteCallbacks.map((callback) => callback(messageId));
- }
- });
- await this.rcClient.subscribeNotifyUser();
- await this.rcClient.onStreamData('stream-notify-user', (ddpMessage: any) => {
- const [, event] = ddpMessage.fields.eventName.split('/');
- const args: any[] = ddpMessage.fields.args
- ? Array.isArray(ddpMessage.fields.args)
- ? ddpMessage.fields.args
- : [ddpMessage.fields.args]
- : [];
- if (event === 'message') {
- const data = args[0];
- if (!data || data?.rid !== this.rid) {
- return;
+ if (event === "typing") {
+ const typingUser = ddpMessage.fields.args[0];
+ const isTyping = ddpMessage.fields.args[1];
+ this.handleTypingEvent({ typingUser, isTyping });
}
- const message = JSON.parse(JSON.stringify(data));
- if( message.ts?.$date) {
- message.ts = message.ts.$date;
+ if (event === "deleteMessage") {
+ const messageId = ddpMessage.fields.args[0]?._id;
+ this.onMessageDeleteCallbacks.map((callback) =>
+ callback(messageId)
+ );
}
- if (!message.ts) {
- message.ts = new Date().toISOString();
+ }
+ );
+ await this.rcClient.subscribeNotifyUser();
+ await this.rcClient.onStreamData(
+ "stream-notify-user",
+ (ddpMessage: any) => {
+ const [, event] = ddpMessage.fields.eventName.split("/");
+ const args: any[] = ddpMessage.fields.args
+ ? Array.isArray(ddpMessage.fields.args)
+ ? ddpMessage.fields.args
+ : [ddpMessage.fields.args]
+ : [];
+ if (event === "message") {
+ const data = args[0];
+ if (!data || data?.rid !== this.rid) {
+ return;
+ }
+ const message = JSON.parse(JSON.stringify(data));
+ if (message.ts?.$date) {
+ message.ts = message.ts.$date;
+ }
+ if (!message.ts) {
+ message.ts = new Date().toISOString();
+ }
+ message.renderType = "blocks";
+ this.onMessageCallbacks.map((callback) => callback(message));
+ } else if (event === "uiInteraction") {
+ this.onUiInteractionCallbacks.forEach((callback) =>
+ callback(args[0])
+ );
}
- message.renderType = 'blocks';
- this.onMessageCallbacks.map((callback) => callback(message));
- } else if (event === 'uiInteraction') {
- this.onUiInteractionCallbacks.forEach((callback) => callback(args[0]));
}
- });
+ );
} catch (err) {
await this.close();
}
@@ -295,9 +310,7 @@ export default class EmbeddedChatApi {
}
async addUiInteractionListener(callback: (data: any) => void) {
- const idx = this.onUiInteractionCallbacks.findIndex(
- (c) => c === callback
- );
+ const idx = this.onUiInteractionCallbacks.findIndex((c) => c === callback);
if (idx !== -1) {
this.onUiInteractionCallbacks[idx] = callback;
} else {
@@ -311,7 +324,10 @@ export default class EmbeddedChatApi {
);
}
- handleTypingEvent({ typingUser, isTyping }: {
+ handleTypingEvent({
+ typingUser,
+ isTyping,
+ }: {
typingUser: string;
isTyping: boolean;
}) {
@@ -339,16 +355,16 @@ export default class EmbeddedChatApi {
async updateUserNameThroughSuggestion(userid: string) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(
`${this.host}/api/v1/users.getUsernameSuggestion`,
{
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'GET',
+ method: "GET",
}
);
@@ -358,11 +374,11 @@ export default class EmbeddedChatApi {
const response2 = await fetch(`${this.host}/api/v1/users.update`, {
body: `{"userId": "${userid}", "data": { "username": "${suggestedUsername.result}" }}`,
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
return await response2.json();
@@ -373,28 +389,28 @@ export default class EmbeddedChatApi {
}
async updateUserUsername(userid: string, username: string) {
- const newUserName = username.replace(/\s/g, '.').toLowerCase();
+ const newUserName = username.replace(/\s/g, ".").toLowerCase();
const usernameRegExp = /[0-9a-zA-Z-_.]+/;
if (usernameRegExp.test(newUserName)) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/users.update`, {
body: `{"userId": "${userid}", "data": { "username": "${newUserName}" }}`,
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
const result = await response.json();
if (
!result.success &&
- result.errorType === 'error-could-not-save-identity'
+ result.errorType === "error-could-not-save-identity"
) {
return await this.updateUserNameThroughSuggestion(userid);
}
@@ -409,16 +425,16 @@ export default class EmbeddedChatApi {
async channelInfo() {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(
`${this.host}/api/v1/rooms.info?roomId=${this.rid}`,
{
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'GET',
+ method: "GET",
}
);
return await response.json();
@@ -427,6 +443,23 @@ export default class EmbeddedChatApi {
}
}
+ async permissionInfo() {
+ try {
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
+ const response = await fetch(`${this.host}/api/v1/permissions.listAll`, {
+ headers: {
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
+ },
+ method: "GET",
+ });
+ return await response.json();
+ } catch (err) {
+ console.error(err);
+ }
+ }
+
async close() {
await this.rcClient.unsubscribeAll();
await this.rcClient.disconnect();
@@ -439,32 +472,36 @@ export default class EmbeddedChatApi {
* fields - json object with properties that have either 1 or 0 to include them or exclude them
* @returns messages
*/
- async getMessages(anonymousMode = false, options: {
- query?: object | undefined;
- field?: object | undefined;
+ async getMessages(
+ anonymousMode = false,
+ options: {
+ query?: object | undefined;
+ field?: object | undefined;
} = {
query: undefined,
- field: undefined
- }, isChannelPrivate = false) {
- const roomType = isChannelPrivate ? 'groups' : 'channels' ;
- const endp = anonymousMode ? 'anonymousread' : 'messages';
+ field: undefined,
+ },
+ isChannelPrivate = false
+ ) {
+ const roomType = isChannelPrivate ? "groups" : "channels";
+ const endp = anonymousMode ? "anonymousread" : "messages";
const query = options?.query
? `&query=${JSON.stringify(options.query)}`
- : '';
+ : "";
const field = options?.field
? `&field=${JSON.stringify(options.field)}`
- : '';
+ : "";
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const messages = await fetch(
`${this.host}/api/v1/${roomType}.${endp}?roomId=${this.rid}${query}${field}`,
{
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'GET',
+ method: "GET",
}
);
return await messages.json();
@@ -474,26 +511,30 @@ export default class EmbeddedChatApi {
}
async getThreadMessages(tmid: string, isChannelPrivate = false) {
- return this.getMessages(false, {
- query: {
- tmid,
+ return this.getMessages(
+ false,
+ {
+ query: {
+ tmid,
+ },
},
- }, isChannelPrivate);
+ isChannelPrivate
+ );
}
async getChannelRoles(isChannelPrivate = false) {
- const roomType = isChannelPrivate ? 'groups' : 'channels';
+ const roomType = isChannelPrivate ? "groups" : "channels";
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const roles = await fetch(
`${this.host}/api/v1/${roomType}.roles?roomId=${this.rid}`,
{
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'GET',
+ method: "GET",
}
);
return await roles.json();
@@ -505,10 +546,10 @@ export default class EmbeddedChatApi {
async sendTypingStatus(username: string, typing: boolean) {
try {
this.rcClient.methodCall(
- 'stream-notify-room',
+ "stream-notify-room",
`${this.rid}/user-activity`,
username,
- typing ? ['user-typing'] : []
+ typing ? ["user-typing"] : []
);
} catch (err) {
console.error(err);
@@ -521,7 +562,7 @@ export default class EmbeddedChatApi {
*/
async sendMessage(message: any, threadId: string) {
const messageObj =
- typeof message === 'string'
+ typeof message === "string"
? {
rid: this.rid,
msg: message,
@@ -534,15 +575,15 @@ export default class EmbeddedChatApi {
messageObj.tmid = threadId;
}
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/chat.sendMessage`, {
body: JSON.stringify({ message: messageObj }),
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
return await response.json();
} catch (err) {
@@ -552,15 +593,15 @@ export default class EmbeddedChatApi {
async deleteMessage(msgId: string) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/chat.delete`, {
body: `{"roomId": "${this.rid}", "msgId": "${msgId}","asUser" : true }`,
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
return await response.json();
} catch (err) {
@@ -570,15 +611,15 @@ export default class EmbeddedChatApi {
async updateMessage(msgId: string, text: string) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/chat.update`, {
body: `{"roomId": "${this.rid}", "msgId": "${msgId}","text" : "${text}" }`,
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
return await response.json();
} catch (err) {
@@ -588,15 +629,15 @@ export default class EmbeddedChatApi {
async starMessage(mid: string) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/chat.starMessage`, {
body: `{"messageId": "${mid}"}`,
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
return await response.json();
} catch (err) {
@@ -606,15 +647,15 @@ export default class EmbeddedChatApi {
async unstarMessage(mid: string) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/chat.unStarMessage`, {
body: `{"messageId": "${mid}"}`,
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
return await response.json();
} catch (err) {
@@ -624,16 +665,16 @@ export default class EmbeddedChatApi {
async getStarredMessages() {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(
`${this.host}/api/v1/chat.getStarredMessages?roomId=${this.rid}`,
{
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'GET',
+ method: "GET",
}
);
return await response.json();
@@ -644,16 +685,16 @@ export default class EmbeddedChatApi {
async getPinnedMessages() {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(
`${this.host}/api/v1/chat.getPinnedMessages?roomId=${this.rid}`,
{
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'GET',
+ method: "GET",
}
);
return await response.json();
@@ -664,15 +705,15 @@ export default class EmbeddedChatApi {
async pinMessage(mid: string) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/chat.pinMessage`, {
body: `{"messageId": "${mid}"}`,
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
return await response.json();
} catch (err) {
@@ -684,15 +725,15 @@ export default class EmbeddedChatApi {
async unpinMessage(mid: string) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/chat.unPinMessage`, {
body: `{"messageId": "${mid}"}`,
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
return await response.json();
} catch (err) {
@@ -702,15 +743,15 @@ export default class EmbeddedChatApi {
async reactToMessage(emoji: string, messageId: string, shouldReact: string) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/chat.react`, {
body: `{"messageId": "${messageId}", "emoji": "${emoji}", "shouldReact": ${shouldReact}}`,
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
return await response.json();
} catch (err) {
@@ -720,15 +761,15 @@ export default class EmbeddedChatApi {
async reportMessage(messageId: string, description: string) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/chat.reportMessage`, {
body: `{"messageId": "${messageId}", "description": "${description}"}`,
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
});
return await response.json();
} catch (err) {
@@ -738,14 +779,14 @@ export default class EmbeddedChatApi {
async findOrCreateInvite() {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/findOrCreateInvite`, {
- method: 'POST',
+ method: "POST",
body: JSON.stringify({ rid: this.rid, days: 1, maxUses: 10 }),
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
});
return await response.json();
@@ -757,26 +798,26 @@ export default class EmbeddedChatApi {
async sendAttachment(
file: File,
fileName: string,
- fileDescription = '',
+ fileDescription = "",
threadId = undefined
) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const form = new FormData();
if (threadId) {
- form.append('tmid', threadId);
+ form.append("tmid", threadId);
}
- form.append('file', file, fileName);
+ form.append("file", file, fileName);
form.append(
- 'description',
- fileDescription.length !== 0 ? fileDescription : ''
+ "description",
+ fileDescription.length !== 0 ? fileDescription : ""
);
const response = fetch(`${this.host}/api/v1/rooms.upload/${this.rid}`, {
- method: 'POST',
+ method: "POST",
body: form,
headers: {
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
}).then((r) => r.json());
return response;
@@ -787,14 +828,14 @@ export default class EmbeddedChatApi {
async me() {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/me`, {
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'GET',
+ method: "GET",
});
return await response.json();
} catch (err) {
@@ -803,18 +844,18 @@ export default class EmbeddedChatApi {
}
async getChannelMembers(isChannelPrivate = false) {
- const roomType = isChannelPrivate ? 'groups' : 'channels';
+ const roomType = isChannelPrivate ? "groups" : "channels";
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(
`${this.host}/api/v1/${roomType}.members?roomId=${this.rid}`,
{
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'GET',
+ method: "GET",
}
);
return await response.json();
@@ -825,16 +866,16 @@ export default class EmbeddedChatApi {
async getSearchMessages(text: string) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(
`${this.host}/api/v1/chat.search?roomId=${this.rid}&searchText=${text}`,
{
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'GET',
+ method: "GET",
}
);
return await response.json();
@@ -853,7 +894,7 @@ export default class EmbeddedChatApi {
...rest
}: any) {
try {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const triggerId = Math.random().toString(32).slice(2, 16);
@@ -863,13 +904,13 @@ export default class EmbeddedChatApi {
`${this.host}/api/apps/ui.interaction/${appId}`,
{
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
body: JSON.stringify({
- type: 'blockAction',
+ type: "blockAction",
actionId,
payload,
container,
@@ -889,28 +930,28 @@ export default class EmbeddedChatApi {
}
async getCommandsList() {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/commands.list`, {
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'GET',
+ method: "GET",
});
const data = await response.json();
return data;
}
- async execCommand({ command, params }: { command: string, params: string; }) {
- const { userId, authToken } = await this.auth.getCurrentUser() || {};
+ async execCommand({ command, params }: { command: string; params: string }) {
+ const { userId, authToken } = (await this.auth.getCurrentUser()) || {};
const response = await fetch(`${this.host}/api/v1/commands.run`, {
headers: {
- 'Content-Type': 'application/json',
- 'X-Auth-Token': authToken,
- 'X-User-Id': userId,
+ "Content-Type": "application/json",
+ "X-Auth-Token": authToken,
+ "X-User-Id": userId,
},
- method: 'POST',
+ method: "POST",
body: JSON.stringify({
command,
params,
diff --git a/packages/api/src/cloneArray.ts b/packages/api/src/cloneArray.ts
index cbdaa185d..ceb43ad43 100644
--- a/packages/api/src/cloneArray.ts
+++ b/packages/api/src/cloneArray.ts
@@ -5,7 +5,7 @@
*/
const cloneArray = (array: any[]) => {
const newArray = [...array].map((item) =>
- typeof item === 'object' ? { ...item } : item
+ typeof item === "object" ? { ...item } : item
);
return newArray;
};
diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts
index aa9ac1d7d..b4ff83a1c 100644
--- a/packages/api/src/index.ts
+++ b/packages/api/src/index.ts
@@ -1 +1 @@
-export { default as EmbeddedChatApi } from './EmbeddedChatApi'
+export { default as EmbeddedChatApi } from "./EmbeddedChatApi";
diff --git a/packages/auth/package.json b/packages/auth/package.json
index 6e49486b5..0eb2f1a77 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -9,7 +9,9 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup -c",
- "dev": "yarn parcel playground/index.html"
+ "dev": "yarn parcel playground/index.html",
+ "format": "prettier --write 'src/' ",
+ "format:check": "prettier --check 'src/' "
},
"author": "",
"license": "ISC",
diff --git a/packages/auth/src/Api.ts b/packages/auth/src/Api.ts
index d124efe0a..78d6c82c7 100644
--- a/packages/auth/src/Api.ts
+++ b/packages/auth/src/Api.ts
@@ -1,58 +1,63 @@
export class ApiError extends Error {
- response: Response;
- constructor(
- response: Response,
- message?: string | undefined,
- options?: ErrorOptions | undefined,
- ...other: any[]
- ) {
- super(message, options, ...other as []);
- this.response = response;
- }
+ response: Response;
+ constructor(
+ response: Response,
+ message?: string | undefined,
+ options?: ErrorOptions | undefined,
+ ...other: any[]
+ ) {
+ super(message, options, ...(other as []));
+ this.response = response;
+ }
}
export class Api {
- baseUrl: string;
- constructor(baseUrl: string) {
- this.baseUrl = baseUrl;
- }
- getFetchConfig = (config: RequestInit) => {
- const headers = {
- 'Content-Type': 'application/json',
- ...(config?.headers || {})
- }
- const requestInit: RequestInit = {
- ...config,
- headers,
- }
- return requestInit;
- }
- async request(method: string = 'GET', endpoint: string, data: any, config: RequestInit) {
- const url = new URL(endpoint, this.baseUrl).toString();
- const response = await fetch(url, {
- body: data ? JSON.stringify(data) : undefined,
- method,
- headers: {
- 'Content-Type': 'application/json'
- }
- });
- if (!response.ok) {
- throw new ApiError(response, "Failed Api Request for "+endpoint);
- }
- const jsonData = await response.json();
- return { data: jsonData };
- }
+ baseUrl: string;
+ constructor(baseUrl: string) {
+ this.baseUrl = baseUrl;
+ }
+ getFetchConfig = (config: RequestInit) => {
+ const headers = {
+ "Content-Type": "application/json",
+ ...(config?.headers || {}),
+ };
+ const requestInit: RequestInit = {
+ ...config,
+ headers,
+ };
+ return requestInit;
+ };
+ async request(
+ method: string = "GET",
+ endpoint: string,
+ data: any,
+ config: RequestInit
+ ) {
+ const url = new URL(endpoint, this.baseUrl).toString();
+ const response = await fetch(url, {
+ body: data ? JSON.stringify(data) : undefined,
+ method,
+ headers: {
+ ...config.headers,
+ },
+ });
+ if (!response.ok) {
+ throw new ApiError(response, "Failed Api Request for " + endpoint);
+ }
+ const jsonData = await response.json();
+ return { data: jsonData };
+ }
- async post(endpoint: string, data: any, config: RequestInit = {}) {
- return this.request('POST', endpoint, data, this.getFetchConfig(config));
- }
- async get(endpoint: string, config: RequestInit = {}) {
- return this.request('GET', endpoint, null, this.getFetchConfig(config));
- }
- async put(endpoint: string, data: any, config: RequestInit = {}) {
- return this.request('PUT', endpoint, data, this.getFetchConfig(config));
- }
- async delete(endpoint: string, config: RequestInit = {}) {
- return this.request('DELETE', endpoint, null, this.getFetchConfig(config));
- }
+ async post(endpoint: string, data: any, config: RequestInit = {}) {
+ return this.request("POST", endpoint, data, this.getFetchConfig(config));
+ }
+ async get(endpoint: string, config: RequestInit = {}) {
+ return this.request("GET", endpoint, null, this.getFetchConfig(config));
+ }
+ async put(endpoint: string, data: any, config: RequestInit = {}) {
+ return this.request("PUT", endpoint, data, this.getFetchConfig(config));
+ }
+ async delete(endpoint: string, config: RequestInit = {}) {
+ return this.request("DELETE", endpoint, null, this.getFetchConfig(config));
+ }
}
diff --git a/packages/auth/src/IRocketChatAuthOptions.ts b/packages/auth/src/IRocketChatAuthOptions.ts
index 06181cdb2..1c452fa4a 100644
--- a/packages/auth/src/IRocketChatAuthOptions.ts
+++ b/packages/auth/src/IRocketChatAuthOptions.ts
@@ -1,7 +1,7 @@
export interface IRocketChatAuthOptions {
- host: string;
- saveToken: (token: string) => Promise;
- getToken: () => Promise;
- deleteToken: () => Promise;
- autoLogin?: boolean;
+ host: string;
+ saveToken: (token: string) => Promise;
+ getToken: () => Promise;
+ deleteToken: () => Promise;
+ autoLogin?: boolean;
}
diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts
index e8b8620a6..5d06758da 100644
--- a/packages/auth/src/auth.ts
+++ b/packages/auth/src/auth.ts
@@ -1,22 +1,20 @@
-import { IRocketChatAuthOptions } from './IRocketChatAuthOptions';
-import RocketChatAuth from './RocketChatAuth';
+import { IRocketChatAuthOptions } from "./IRocketChatAuthOptions";
+import RocketChatAuth from "./RocketChatAuth";
const rocketChatAuth = ({
- host,
- saveToken,
- getToken,
- deleteToken,
- autoLogin,
+ host,
+ saveToken,
+ getToken,
+ deleteToken,
+ autoLogin,
}: IRocketChatAuthOptions) => {
- return new RocketChatAuth({
- host,
- saveToken,
- getToken,
- deleteToken,
- autoLogin,
- });
-}
+ return new RocketChatAuth({
+ host,
+ saveToken,
+ getToken,
+ deleteToken,
+ autoLogin,
+ });
+};
-export {
- rocketChatAuth
-}
+export { rocketChatAuth };
diff --git a/packages/auth/src/getAuthorizationUrl.ts b/packages/auth/src/getAuthorizationUrl.ts
index 1c9346706..32e9a5e15 100644
--- a/packages/auth/src/getAuthorizationUrl.ts
+++ b/packages/auth/src/getAuthorizationUrl.ts
@@ -1,12 +1,15 @@
const getAuthorizationUrl = (oauthService: any) => {
- if (oauthService.authorizePath?.startsWith('http')){
- return oauthService.authorizePath;
- }
- if (oauthService.serverURL) {
- return new URL(oauthService.authorizePath, oauthService.serverURL).toString();
- } else {
- return oauthService.authorizePath;
- }
-}
+ if (oauthService.authorizePath?.startsWith("http")) {
+ return oauthService.authorizePath;
+ }
+ if (oauthService.serverURL) {
+ return new URL(
+ oauthService.authorizePath,
+ oauthService.serverURL
+ ).toString();
+ } else {
+ return oauthService.authorizePath;
+ }
+};
export default getAuthorizationUrl;
diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts
index 91b9edfc0..ff36dfb6a 100644
--- a/packages/auth/src/index.ts
+++ b/packages/auth/src/index.ts
@@ -1,4 +1,4 @@
-export * from './auth';
-export { default as RocketChatAuth } from './RocketChatAuth';
-export * from './IRocketChatAuthOptions';
-export {ApiError} from './Api';
\ No newline at end of file
+export * from "./auth";
+export { default as RocketChatAuth } from "./RocketChatAuth";
+export * from "./IRocketChatAuthOptions";
+export { ApiError } from "./Api";
diff --git a/packages/auth/src/loginWithOAuthServiceToken.ts b/packages/auth/src/loginWithOAuthServiceToken.ts
index bd823505b..64518ee44 100644
--- a/packages/auth/src/loginWithOAuthServiceToken.ts
+++ b/packages/auth/src/loginWithOAuthServiceToken.ts
@@ -1,17 +1,17 @@
import { Api } from "./Api";
const loginWithOAuthServiceToken = async (
- config: {
- api: Api
- },
- credentials: {
- service: string;
- access_token: string;
- [key:string]: string;
- }
+ config: {
+ api: Api;
+ },
+ credentials: {
+ service: string;
+ access_token: string;
+ [key: string]: string;
+ }
) => {
- const response = await config.api.post('/api/v1/login', credentials)
- return response.data;
-}
+ const response = await config.api.post("/api/v1/login", credentials);
+ return response.data;
+};
export default loginWithOAuthServiceToken;
diff --git a/packages/auth/src/loginWithPassword.ts b/packages/auth/src/loginWithPassword.ts
index c85a2ee1f..7843efb38 100644
--- a/packages/auth/src/loginWithPassword.ts
+++ b/packages/auth/src/loginWithPassword.ts
@@ -1,23 +1,25 @@
import { Api } from "./Api";
const loginWithPassword = async (
- config : {
- api: Api;
- }, {
- user,
- password,
- code,
- }: {
- user: string;
- password: string;
- code?: string | number;
-}) => {
- const response = await config.api.post('/api/v1/login', {
- user,
- password,
- code
- })
- return response.data;
-}
+ config: {
+ api: Api;
+ },
+ {
+ user,
+ password,
+ code,
+ }: {
+ user: string;
+ password: string;
+ code?: string | number;
+ }
+) => {
+ const response = await config.api.post("/api/v1/login", {
+ user,
+ password,
+ code,
+ });
+ return response.data;
+};
export default loginWithPassword;
diff --git a/packages/auth/src/loginWithResumeToken.ts b/packages/auth/src/loginWithResumeToken.ts
index e85407dbd..b9b1f2b6e 100644
--- a/packages/auth/src/loginWithResumeToken.ts
+++ b/packages/auth/src/loginWithResumeToken.ts
@@ -1,15 +1,15 @@
import { Api } from "./Api";
const loginWithResumeToken = async (
- config: {
- api: Api
- },
- credentials: {
- resume: string
- }
+ config: {
+ api: Api;
+ },
+ credentials: {
+ resume: string;
+ }
) => {
- const response = await config.api.post('/api/v1/login', credentials)
- return response.data;
-}
+ const response = await config.api.post("/api/v1/login", credentials);
+ return response.data;
+};
export default loginWithResumeToken;
diff --git a/packages/auth/src/loginWithRocketChatOAuth.ts b/packages/auth/src/loginWithRocketChatOAuth.ts
index 7eea59823..de01ae295 100644
--- a/packages/auth/src/loginWithRocketChatOAuth.ts
+++ b/packages/auth/src/loginWithRocketChatOAuth.ts
@@ -52,17 +52,17 @@ width=800,height=600,left=-1000,top=-1000,rel=opener`;
expiresIn,
serviceName,
});
- popup.close();
+ popup.close();
resolve(response.data);
}
};
window.addEventListener("message", onMessage);
const checkInterval = setInterval(() => {
- if (popup.closed) {
- clearInterval(checkInterval);
- window.removeEventListener("message", onMessage);
- }
- }, 1000);
+ if (popup.closed) {
+ clearInterval(checkInterval);
+ window.removeEventListener("message", onMessage);
+ }
+ }, 1000);
} else {
throw new Error("Popup blocked");
}
diff --git a/packages/auth/src/utils/constants.ts b/packages/auth/src/utils/constants.ts
index 25b9dd64f..846757b54 100644
--- a/packages/auth/src/utils/constants.ts
+++ b/packages/auth/src/utils/constants.ts
@@ -1 +1 @@
-export const ROCKETCHAT_APP_ID = '4c977b2e-eda2-4627-8bfe-2d0358304a79';
+export const ROCKETCHAT_APP_ID = "4c977b2e-eda2-4627-8bfe-2d0358304a79";
diff --git a/packages/auth/src/utils/getRCAppBaseURL.ts b/packages/auth/src/utils/getRCAppBaseURL.ts
index 4850de224..6416e06a6 100644
--- a/packages/auth/src/utils/getRCAppBaseURL.ts
+++ b/packages/auth/src/utils/getRCAppBaseURL.ts
@@ -1,6 +1,6 @@
-import { ROCKETCHAT_APP_ID } from "./constants"
+import { ROCKETCHAT_APP_ID } from "./constants";
export const getRCAppBaseURL = (host: string) => {
- const url = new URL(`api/apps/public/${ROCKETCHAT_APP_ID}`, host);
- return url.toString();
-}
+ const url = new URL(`api/apps/public/${ROCKETCHAT_APP_ID}`, host);
+ return url.toString();
+};
diff --git a/packages/auth/src/utils/getRCAppInfo.ts b/packages/auth/src/utils/getRCAppInfo.ts
index ea4e0d617..191cac93d 100644
--- a/packages/auth/src/utils/getRCAppInfo.ts
+++ b/packages/auth/src/utils/getRCAppInfo.ts
@@ -1,12 +1,12 @@
import { getRCAppBaseURL } from "./getRCAppBaseURL";
export const getRCAppInfo = async (host: string) => {
- const rcAppBaseUrl = getRCAppBaseURL(host);
- const infoUrl = rcAppBaseUrl + '/info';
- const response = await fetch(infoUrl.toString());
- if (!response.ok) {
- return null;
- }
- const info = await response.json();
- return info;
-}
+ const rcAppBaseUrl = getRCAppBaseURL(host);
+ const infoUrl = rcAppBaseUrl + "/info";
+ const response = await fetch(infoUrl.toString());
+ if (!response.ok) {
+ return null;
+ }
+ const info = await response.json();
+ return info;
+};
diff --git a/packages/auth/src/utils/getRCAuthorizeURL.ts b/packages/auth/src/utils/getRCAuthorizeURL.ts
index a8c0a71c2..6ddec5606 100644
--- a/packages/auth/src/utils/getRCAuthorizeURL.ts
+++ b/packages/auth/src/utils/getRCAuthorizeURL.ts
@@ -1,8 +1,12 @@
-export const getRCAuthorizeURL = (host: string, redirectUri: string, clientId: string) => {
- const url = new URL(`oauth/authorize`, host);
- url.searchParams.set('response_type', 'code');
- url.searchParams.set('client_id', clientId);
- url.searchParams.set('redirect_uri', redirectUri);
- url.searchParams.set('state', encodeURIComponent(window.location.origin));
- return url.toString();
-}
+export const getRCAuthorizeURL = (
+ host: string,
+ redirectUri: string,
+ clientId: string
+) => {
+ const url = new URL(`oauth/authorize`, host);
+ url.searchParams.set("response_type", "code");
+ url.searchParams.set("client_id", clientId);
+ url.searchParams.set("redirect_uri", redirectUri);
+ url.searchParams.set("state", encodeURIComponent(window.location.origin));
+ return url.toString();
+};
diff --git a/packages/e2e-react/.eslintrc.cjs b/packages/e2e-react/.eslintrc.cjs
new file mode 100644
index 000000000..d6c953795
--- /dev/null
+++ b/packages/e2e-react/.eslintrc.cjs
@@ -0,0 +1,18 @@
+module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:react-hooks/recommended',
+ ],
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
+ parser: '@typescript-eslint/parser',
+ plugins: ['react-refresh'],
+ rules: {
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+}
diff --git a/packages/e2e-react/.gitignore b/packages/e2e-react/.gitignore
new file mode 100644
index 000000000..b88c8135a
--- /dev/null
+++ b/packages/e2e-react/.gitignore
@@ -0,0 +1,28 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/packages/e2e-react/README.md b/packages/e2e-react/README.md
new file mode 100644
index 000000000..181d94ad9
--- /dev/null
+++ b/packages/e2e-react/README.md
@@ -0,0 +1 @@
+# E2E EmbeddedChat setup
diff --git a/packages/e2e-react/index.html b/packages/e2e-react/index.html
new file mode 100644
index 000000000..e4b78eae1
--- /dev/null
+++ b/packages/e2e-react/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + React + TS
+
+
+
+
+
+
diff --git a/packages/e2e-react/package.json b/packages/e2e-react/package.json
new file mode 100644
index 000000000..40a454323
--- /dev/null
+++ b/packages/e2e-react/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "e2e-react",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "preview": "vite preview",
+ "test": "playwright test",
+ "format": "prettier --write 'src/' ",
+ "format:check": "prettier --check 'src/' "
+ },
+ "dependencies": {
+ "@embeddedchat/react": "workspace:*",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.41.2",
+ "@types/node": "^20.11.19",
+ "@types/react": "^18.2.55",
+ "@types/react-dom": "^18.2.19",
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
+ "@typescript-eslint/parser": "^6.21.0",
+ "@vitejs/plugin-react": "^4.2.1",
+ "eslint": "^8.56.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.5",
+ "typescript": "^5.2.2",
+ "vite": "^5.1.0"
+ }
+}
diff --git a/packages/e2e-react/playwright.config.ts b/packages/e2e-react/playwright.config.ts
new file mode 100644
index 000000000..a91deee96
--- /dev/null
+++ b/packages/e2e-react/playwright.config.ts
@@ -0,0 +1,77 @@
+import { defineConfig, devices } from '@playwright/test';
+
+/**
+ * Read environment variables from file.
+ * https://github.com/motdotla/dotenv
+ */
+// require('dotenv').config();
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: './tests',
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* Retry on CI only */
+ retries: process.env.CI ? 2 : 0,
+ /* Opt out of parallel tests on CI. */
+ workers: process.env.CI ? 1 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: 'html',
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: 'http://127.0.0.1:5173',
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: 'on-first-retry',
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+
+ // {
+ // name: 'firefox',
+ // use: { ...devices['Desktop Firefox'] },
+ // },
+
+ // {
+ // name: 'webkit',
+ // use: { ...devices['Desktop Safari'] },
+ // },
+
+ /* Test against mobile viewports. */
+ // {
+ // name: 'Mobile Chrome',
+ // use: { ...devices['Pixel 5'] },
+ // },
+ // {
+ // name: 'Mobile Safari',
+ // use: { ...devices['iPhone 12'] },
+ // },
+
+ /* Test against branded browsers. */
+ // {
+ // name: 'Microsoft Edge',
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
+ // },
+ // {
+ // name: 'Google Chrome',
+ // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
+ // },
+ ],
+
+ /* Run your local dev server before starting the tests */
+ webServer: {
+ command: 'yarn dev',
+ url: 'http://127.0.0.1:5173',
+ reuseExistingServer: !process.env.CI,
+ },
+});
diff --git a/packages/e2e-react/public/vite.svg b/packages/e2e-react/public/vite.svg
new file mode 100644
index 000000000..e7b8dfb1b
--- /dev/null
+++ b/packages/e2e-react/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/e2e-react/src/App.tsx b/packages/e2e-react/src/App.tsx
new file mode 100644
index 000000000..5be050dd0
--- /dev/null
+++ b/packages/e2e-react/src/App.tsx
@@ -0,0 +1,14 @@
+// @ts-expect-error no types served yet
+import { EmbeddedChat } from "@embeddedchat/react";
+
+function App() {
+ return (
+
+ );
+}
+
+export default App;
diff --git a/packages/e2e-react/src/main.tsx b/packages/e2e-react/src/main.tsx
new file mode 100644
index 000000000..95e2bdc2c
--- /dev/null
+++ b/packages/e2e-react/src/main.tsx
@@ -0,0 +1,9 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App.tsx";
+
+ReactDOM.createRoot(document.getElementById("root")!).render(
+
+
+
+);
diff --git a/packages/e2e-react/src/vite-env.d.ts b/packages/e2e-react/src/vite-env.d.ts
new file mode 100644
index 000000000..11f02fe2a
--- /dev/null
+++ b/packages/e2e-react/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/packages/e2e-react/tests/example.spec.ts b/packages/e2e-react/tests/example.spec.ts
new file mode 100644
index 000000000..4e9ba0589
--- /dev/null
+++ b/packages/e2e-react/tests/example.spec.ts
@@ -0,0 +1,18 @@
+import { test, expect } from '@playwright/test';
+
+test('EmbeddedChat should render', async ({ page }) => {
+ await page.goto('/');
+ await expect(page.locator('.ec-embedded-chat')).toBeVisible();
+});
+
+test('EmbeddedChat has a title', async ({ page }) => {
+ await page.goto('/');
+ await expect(page.locator('.ec-chat-header--channelDescription')).toHaveText('Login to chat');
+});
+
+test('EmbeddedChat has messages', async ({ page }) => {
+ await page.goto('/');
+
+ await page.waitForSelector('.ec-message');
+ expect(await page.locator('.ec-message').count()).toBeGreaterThan(0);
+});
diff --git a/packages/e2e-react/tsconfig.json b/packages/e2e-react/tsconfig.json
new file mode 100644
index 000000000..a7fc6fbf2
--- /dev/null
+++ b/packages/e2e-react/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/packages/e2e-react/tsconfig.node.json b/packages/e2e-react/tsconfig.node.json
new file mode 100644
index 000000000..97ede7ee6
--- /dev/null
+++ b/packages/e2e-react/tsconfig.node.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "strict": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/packages/e2e-react/vite.config.ts b/packages/e2e-react/vite.config.ts
new file mode 100644
index 000000000..028287cd5
--- /dev/null
+++ b/packages/e2e-react/vite.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ define: {
+ 'process.env': {}
+ }
+})
diff --git a/packages/htmlembed/package.json b/packages/htmlembed/package.json
index ce5b68820..33a837024 100644
--- a/packages/htmlembed/package.json
+++ b/packages/htmlembed/package.json
@@ -8,7 +8,9 @@
"private": true,
"scripts": {
"build": "vite build && node postbuild.cjs",
- "preview": "npm run build && vite preview --port=4001"
+ "preview": "npm run build && vite preview --port=4001",
+ "format": "prettier --write 'src/' ",
+ "format:check": "prettier --check 'src/' "
},
"dependencies": {
"@embeddedchat/react": "0.1.12",
diff --git a/packages/htmlembed/src/EmbeddedChat.jsx b/packages/htmlembed/src/EmbeddedChat.jsx
index a771bfd1b..71fae93f4 100644
--- a/packages/htmlembed/src/EmbeddedChat.jsx
+++ b/packages/htmlembed/src/EmbeddedChat.jsx
@@ -1,37 +1,39 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import { EmbeddedChat as EmbeddedChatComponent } from '@embeddedchat/react';
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { EmbeddedChat as EmbeddedChatComponent } from "@embeddedchat/react";
const EmbeddedChat = {
- renderInElementWithId(config, id) {
- if (!id) {
- throw new Error("Please provide a valid id of the element to render embeddedchat");
- }
- ReactDOM.createRoot(document.getElementById(id)).render(
-
-
-
- )
- },
- renderInElementWithSelector(config, selector) {
- if(!selector) {
- throw new Error("Please provide a valid selector to render embeddedchat");
- }
- ReactDOM.createRoot(document.querySelector(selector)).render(
-
-
-
- )
- },
- renderInElement(config, element) {
- if (!element) {
- throw new Error("Please provide a valid element to render embeddedchat");
- }
- ReactDOM.createRoot(element).render(
-
-
-
- )
- }
-}
+ renderInElementWithId(config, id) {
+ if (!id) {
+ throw new Error(
+ "Please provide a valid id of the element to render embeddedchat"
+ );
+ }
+ ReactDOM.createRoot(document.getElementById(id)).render(
+
+
+
+ );
+ },
+ renderInElementWithSelector(config, selector) {
+ if (!selector) {
+ throw new Error("Please provide a valid selector to render embeddedchat");
+ }
+ ReactDOM.createRoot(document.querySelector(selector)).render(
+
+
+
+ );
+ },
+ renderInElement(config, element) {
+ if (!element) {
+ throw new Error("Please provide a valid element to render embeddedchat");
+ }
+ ReactDOM.createRoot(element).render(
+
+
+
+ );
+ },
+};
export default EmbeddedChat;
diff --git a/packages/rc-app/endpoints/CallbackEndpoint.ts b/packages/rc-app/endpoints/CallbackEndpoint.ts
index c230a716e..1a5e6560e 100644
--- a/packages/rc-app/endpoints/CallbackEndpoint.ts
+++ b/packages/rc-app/endpoints/CallbackEndpoint.ts
@@ -55,30 +55,42 @@ export class CallbackEndpoint extends ApiEndpoint {
const response = await http.post(tokenUrl, {
content: formData.toString(),
headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
+ "Content-Type": "application/x-www-form-urlencoded",
},
- })
+ });
if (response.statusCode !== 200) {
- return {
- status: response.statusCode,
- content: await getCallbackContent(read, null, origin, response.data.error_description || 'Unknown'),
- headers: {
- 'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-src 'self'; font-src 'self'; object-src 'none'",
- }
- }
+ return {
+ status: response.statusCode,
+ content: await getCallbackContent(
+ read,
+ null,
+ origin,
+ response.data.error_description || "Unknown"
+ ),
+ headers: {
+ "Content-Security-Policy":
+ "default-src 'self'; script-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-src 'self'; font-src 'self'; object-src 'none'",
+ },
+ };
}
return {
status: 200,
- content: await getCallbackContent(read, {
- accessToken: response.data?.access_token,
- expiresIn: response.data?.expires_in,
- serviceName: customOAuthName,
- }, origin, false),
+ content: await getCallbackContent(
+ read,
+ {
+ accessToken: response.data?.access_token,
+ expiresIn: response.data?.expires_in,
+ serviceName: customOAuthName,
+ },
+ origin,
+ false
+ ),
headers: {
- 'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-src 'self'; font-src 'self'; object-src 'none'",
- }
+ "Content-Security-Policy":
+ "default-src 'self'; script-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-src 'self'; font-src 'self'; object-src 'none'",
+ },
};
}
}
diff --git a/packages/rc-app/endpoints/InfoEndpoint.ts b/packages/rc-app/endpoints/InfoEndpoint.ts
index 8548bc8a1..ab0a69dd9 100644
--- a/packages/rc-app/endpoints/InfoEndpoint.ts
+++ b/packages/rc-app/endpoints/InfoEndpoint.ts
@@ -52,8 +52,8 @@ export class InfoEndpoint extends ApiEndpoint {
client_id: !!client_id,
client_secret: !!client_secret,
custom_oauth_name: !!serviceName,
- }
- }
- }
+ },
+ },
+ };
}
}
diff --git a/packages/rc-app/lib/getAllowedOrigins.ts b/packages/rc-app/lib/getAllowedOrigins.ts
index ea30e038c..aa94c4d7f 100644
--- a/packages/rc-app/lib/getAllowedOrigins.ts
+++ b/packages/rc-app/lib/getAllowedOrigins.ts
@@ -1,8 +1,13 @@
export const getAllowedOrigins = async (read) => {
- const allowedOrigins = await read.getEnvironmentReader().getSettings().getValueById("allowed-origins");
+ const allowedOrigins = await read
+ .getEnvironmentReader()
+ .getSettings()
+ .getValueById("allowed-origins");
if (!allowedOrigins) {
return [];
}
- const origins = allowedOrigins.split(',').filter((domain) => !!domain.trim());
+ const origins = allowedOrigins
+ .split(",")
+ .filter((domain) => !!domain.trim());
return origins;
-}
+};
diff --git a/packages/rc-app/lib/getCallbackContent.ts b/packages/rc-app/lib/getCallbackContent.ts
index c8c1a2898..165aa1c1b 100644
--- a/packages/rc-app/lib/getCallbackContent.ts
+++ b/packages/rc-app/lib/getCallbackContent.ts
@@ -5,22 +5,27 @@ interface ICredentials {
accessToken: string;
expiresIn: number;
serviceName: string;
-};
+}
-export const getCallbackContent = async (read: IRead, credentials: ICredentials | null, origin: string, error) => {
+export const getCallbackContent = async (
+ read: IRead,
+ credentials: ICredentials | null,
+ origin: string,
+ error
+) => {
const { accessToken, expiresIn = 3600, serviceName } = credentials || {};
- const isAllowed = await isAllowedOrigin(read, origin);;
+ const isAllowed = await isAllowedOrigin(read, origin);
let config: any = {};
if (error) {
config = {
success: false,
error,
- }
+ };
} else if (!isAllowed) {
config = {
success: false,
- error: 'origin not allowed',
- }
+ error: "origin not allowed",
+ };
} else {
config = {
success: true,
@@ -29,8 +34,8 @@ export const getCallbackContent = async (read: IRead, credentials: ICredentials
accessToken,
expiresIn,
serviceName,
- }
- }
+ },
+ };
}
const closeLinkHtml = `${
config.success ? "Login Successful" : "Login Failed: " + config.error
@@ -66,4 +71,4 @@ export const getCallbackContent = async (read: IRead, credentials: ICredentials