A package wrapper around the React chat library, used to integrate chat into Vue and Angular apps.
Install
npm install chat-wrapper --save
After installing the wrapper, all the necessary packages for the chat will be automatically installed.
Also, to integrate store chat into the application, you need to install reduxjs/toolkit:
npm install @reduxjs/toolkit --save
Imports
Import the components and constants from the package into the file:
import { ChatConnector, ChatListConnector, LS_TOKEN_KEY, chatStore, ChatRootState } from "chat-wrapper";
Important
After logging the user into your app, you should set the access token in the local storage of the browser for the chat components:
localStorage.setItem(LS_TOKEN_KEY, 'access_token');
After calling the function to refresh a token from your app, the new received token must be installed in local storage of the browser under the key LS_TOKEN_KEY
and returned from the function.
ChatConnector Component
import { ChatConnector } from "chat-wrapper";
<ChatConnector
opponent_id="opponent_id"
user_id="user_id"
user_locale="locale"
isOnlyChat={true}
cbHandleCloseChat={cbCloseChat}
handleRefreshToken={cbRefreshToken}
classHeader="customCSSClass"
classMessages="customCSSClass"
/>
Chat props
prop | default | type | description |
---|
opponent_id | none | string | User opponent ID |
user_id | none | string | User id got from the access token by decoding |
user_locale | ru/en | string | Chat interface language. The browser language is set by default |
isOnlyChat | none | boolean | true value is set when only the chat close functionality is used. false allows more chat functionality |
cbHandleCloseChat | none | function | A callback function that is called when the user clicks the close chat button |
handleRefreshToken | none | function | An asynchronous callback function that is called if the chat API call returns an error. Takes an axios error as an argument. Should return the new user token |
classHeader | " " | string | Adds a custom style class for the Chat header |
classMessages | " " | string | Adds a custom style class for the box with messages |
ChatListConnector Component
import { ChatListConnector } from "chat-wrapper";
<ChatListConnector
user_id="user_id"
user_locale="locale"
isOnlyChatList={true}
cbHandleCloseChatList={handleCloseList}
handleRefreshToken={handleRefreshToken}
classList="customCSSClass"
cbHandleOpenChat={handleOpenChat}
/>
ChatListConnector props
prop | default | type | description |
---|
user_id | none | string | User id got from the access token by decoding |
user_locale | ru/en | string | ChatList interface language. The browser language is set by default. |
isOnlyChatList | none | boolean | true value is set when only the chat list close functionality is used. false allows more chat list functionality. |
cbHandleCloseChatList | none | function | A callback function that is called when the user clicks the close chat button. |
cbHandleOpenChat | none | function | A callback function that is called when the user clicks on a specific chat in the list. Takes an object as a function argument: {chat_id:string; opponent_id:string} |
handleRefreshToken | none | function | An asynchronous callback function that is called if the chat API call returns an error. Takes an axios error as an argument. Should return the new user token. |
classList | " " | string | Adds a custom style class for the ChatList wrapper |
Integration into Vue project
Setting up configuration files
To integrate the React library into the project, you should configure vite to work with react and Vue:
npm install @vitejs/plugin-react
In the vite.config.ts file, add a condition for processing files for React:
//vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [vue(), react()],
});
Connecting redux store
A store redux plugin is being created to connect chat to the store project:
//storePlugin.ts
import { App, reactive } from "vue";
import { EnhancedStore } from "@reduxjs/toolkit";
import { ChatRootState } from "chat-wrapper"; //type for store chat
export const storeKey = Symbol("Redux-Store");
export const createRedux = (store: EnhancedStore) => {
const rootStore = reactive<{ state: ChatRootState }>({
state: store.getState(),
});
const plugin = {
install: (app: App) => {
app.provide<{ state: ChatRootState }>(storeKey, rootStore);
store.subscribe(() => {
rootStore.state = store.getState();
});
},
};
return plugin;
};
In the main.ts file of the app, the chat store and plugin are imported, then connected to the app for use:
//main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { createRedux } from "./storePlugin";
import { chatStore } from "chat-wrapper";
const app = createApp(App);
app.use(createRedux(chatStore)); // Connect the plugin from Redux Store in the app
app.mount("#app");
Using Connectors
A component is created into which the chat connector from the wrapper package will be imported:
//ChatComponent.vue
<template>
<div id="app"></div>
</template>
<script lang="ts">
import { onBeforeUnmount, onMounted } from "vue";
import { LS_TOKEN_KEY, ChatConnector} from "chat-wrapper";
export default {
name: "ChatComponent",
setup() {
let reactComponent: InstanceType<typeof ChatConnector> | null = null;
onMounted(() => {
const appElement = document.getElementById("app") as HTMLElement;
reactComponent = new ChatConnector(appElement);
localStorage.setItem(LS_TOKEN_KEY, 'access_token');
reactComponent.render({
opponent_id: "opponent_id",
cbHandleCloseChat: () => {},
handleRefreshToken: () => {},
user_locale: "user_locale",
isOnlyChat: true,
user_id: "user_id",
});
});
onBeforeUnmount(() => {
if (reactComponent) {
reactComponent.unmount();
}
});
return {};
},
};
</script>
And for the chat list connector the example would look like this:
//ChatListComponent.vue
<template>
<div id="app"></div>
</template>
<script lang="ts">
import { onBeforeUnmount, onMounted } from "vue";
import { LS_TOKEN_KEY, ChatListConnector } from "chat-wrapper";
export default {
name: "ChatListComponent",
setup() {
let reactComponent: InstanceType<typeof ChatListConnector> | null = null;
onMounted(() => {
const appElement = document.getElementById("app") as HTMLElement;
reactComponent = new ChatListConnector(appElement);
localStorage.setItem(LS_TOKEN_KEY, 'access_token');
reactComponent.render({
cbHandleOpenChat: () => {},
cbHandleCloseChatList: () => {},
handleRefreshToken: () => {},
user_locale: "user_locale",
isOnlyChatList: true,
user_id: "user_id",
});
});
onBeforeUnmount(() => {
if (reactComponent) {
reactComponent.unmount();
}
});
return {};
},
};
</script>
In the App.vue project file you need to import the chat files:
//App.vue
<template>
<div>
<ChatComponent />
</div>
</template>
<script>
import ChatComponent from "./components/ChatComponent.vue";
import ChatListComponent from "./components/ChatListComponent.vue";
export default {
name: "App",
components: {
ChatComponent, ChatListComponent
},
};
</script>
Integration into Angular project
Setting up configuration files
In order to be able to create files with the .tsx extension, you need to make changes to the tsconfig.json file:
//tsconfig.json
{
"compilerOptions": {
...
"jsx": "react"
},
"angularCompilerOptions": {
...
}
}
Connecting redux store
You should import the chat store and connect it to the project; to do this, you should create the file src/redux-store.service.ts
//src/redux-store.service.ts
import { Injectable } from '@angular/core';
import { chatStore } from 'chat-wrapper';
@Injectable({
providedIn: 'root',
})
export class ReduxStoreService {
getStore() {
return chatStore;
}
}
Using Connectors
Create a component file into which you will import the chat connectors:
//ChatWrapper.tsx
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
Output,
SimpleChanges,
ViewChild,
ViewEncapsulation,
} from "@angular/core";
import { LS_TOKEN_KEY, ChatConnector } from "chat-wrapper";
import { createRoot } from "react-dom/client";
const containerElementRef = "customReactComponentContainer";
@Component({
selector: "chat-wrapper",
template: `<div #${containerElementRef}></div>`,
encapsulation: ViewEncapsulation.None,
})
export class ChatWrapper implements OnChanges, OnDestroy, AfterViewInit {
@ViewChild(containerElementRef, { static: true }) containerRef!: ElementRef;
@Output() public componentClick = new EventEmitter<void>();
@Input() opponentId: string = "opponent_id";
@Input() userId: string = "user_id";
@Input() userLocale: string = "user_locale";
@Input() isOnlyChat: boolean = true;
private root: ReturnType<typeof createRoot> = {} as ReturnType<
typeof createRoot
>;
private chatConnector!: InstanceType<typeof ChatConnector>;
ngOnChanges(changes: SimpleChanges): void {
if (this.chatConnector) {
this.render();
}
}
ngAfterViewInit() {
this.chatConnector = new ChatConnector(this.containerRef.nativeElement);
this.render();
}
ngOnDestroy() {
if (this.chatConnector) {
this.chatConnector.unmount();
}
}
private render() {
localStorage.setItem(LS_TOKEN_KEY, 'access_token');
this.chatConnector.render({
opponent_id: this.opponentId,
user_id: this.userId,
user_locale: this.userLocale,
isOnlyChat: this.isOnlyChat,
cbHandleCloseChat: () => {
this.componentClick.emit();
},
handleRefreshToken: () => {},
});
}
}
Then you should import the chat wrapper component into the app files:
// app/app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { ChatWrapper } from '../ChatWrapper';
@Component({
selector: 'app-root',
imports: [RouterOutlet, ChatWrapper],
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent {
...
}
// app/app.component.html
<chat-wrapper></chat-wrapper>
<router-outlet />