rn-webim-chat
Version:
Webim chat wrapper for React-Native. Supports Android and iOS. Fixed issues for native platforms build that are present in the official package.
413 lines (322 loc) • 11.9 kB
Markdown
Implementation of [webim sdk](https://webim.ru/) for [react-native](https://github.com/facebook/react-native)
_Inspired by [volga-volga/react-native-webim](https://github.com/volga-volga/react-native-webim)_
<!-- BADGES/ -->
[](https://github.com/mav10/rn-webim-chat/actions/workflows/npm-publish.yml)
<span class="badge-npmversion"><a href="https://www.npmjs.com/package/rn-webim-chat" title="View this project on NPM"><img src="https://badge.fury.io/js/rn-webim-chat.svg" alt="NPM version" /></a></span>
<span class="badge-npmdownloads"><a href="https://www.npmjs.com/package/rn-webim-chat" title="View this project on NPM"><img alt="npm" src="https://img.shields.io/npm/dm/rn-webim-chat"></a></span>
<!-- /BADGES -->
___



- Requires React Native version 0.60.0, or later.
- Supports iOS 10.0, or later.
Via NPM
```sh
npm install rn-webim-chat
```
Via Yarn
```sh
yarn add rn-webim-chat
```
- add `WebimClientLibrary` to Podfile with specific version (_Wrapper was written for v3.37.4_)
- pod install
see [example Podfile](./example/ios/Podfile)
Since the official [WebimClientLibrary](https://github.com/webim/webim-client-sdk-ios) is written is Swift, you need to have Swift enabled in your iOS project. If you already have any .swift files, you are good to go. Otherwise, create a new empty Swift source file in Xcode, and allow it to create the neccessary bridging header when prompted.
## Example
In [example folder](./example) there is simple workflow how to:
- Start and destroy session
- Resume and Pause session
- Get and Send messages
- Rate operator
- Handle errors
How it looks like you can see here
It is achieved with [simple UI](./example/src/simple) (just test common methods)
<table align="Center">
<tr>
<td>Not init session</td>
<td>Requested messages</td>
</tr>
<tr>
<td><img src="doc/img.png" width=400 height=760></td>
<td><img src="doc/messages.png" width=400 height=760></td>
</tr>
</table>
<table align="Center">
<tr>
<td>Error getMessages (as session is null)</td>
<td>Error sendMessage (as session is null)</td>
</tr>
<tr>
<td><img src="doc/error_1.png" width=400 height=760></td>
<td><img src="doc/error_2.png" width=400 height=760></td>
</tr>
</table>

_Also there is another [example with chat UI](./example/src/withCustomUI) [`react-native-gifted-chat`](https://github.com/FaridSafi/react-native-gifted-chat)_
**Important:** All methods are promise based and can throw exceptions.
List of error codes will be provided later as get COMMON for both platform.
```ts
import { RNWebim } from 'rn-webim-chat';
await RNWebim.initSession(builderParams: SessionBuilderParams)
```
**SessionBuilderParams:**
- accountName (required) - name of your account in webim system
- location (required) - name of location. For example "mobile"
- accountJSON - encrypted json with user data. See [**Start chat with user data**](
- clearVisitorData - clear visitor data before start chat
- storeHistoryLocally - cache messages in local store
- title - title for chat in webim web panel
- providedAuthorizationToken - user token. Session will not start with wrong token. Read webim documentation
- pushToken - FCM token is enough - but Apple pushes will come through APN, so you are not able to process them in app by default.
- appVersion - version of your Application
- prechat - some additional fields to prechat
If you have already initialized a session you should **resume** it to consume and send messages, get actual information by listeners etc.
**NOTE:** _After that execution operator on web chat will get message that user opens a chat._
```ts
import { RNWebim } from 'rn-webim-chat';
await RNWebim.resumeSession()
```
If you have already initialized a session you should **resume** it to consume and send messages, get actual information by listeners etc.
After that execution operator on web chat will get message that user opens a chat.
```ts
import { RNWebim } from 'rn-webim-chat';
await RNWebim.pauseSession()
```
```js
import { RNWebim, WebimEvents} from 'rn-webim-chat';
const listener = RNWebim.addNewMessageListener(({ msg }) => {
// do something
});
// usubscribe
listener.remove();
// or
const listener2 = RNWebim.addListener(WebimEvents.NEW_MESSAGE, ({ msg }) => {
// do something
});
```
Supported events (`WebimEvents`):
- WebimEvents.NEW_MESSAGE;
- WebimEvents.REMOVE_MESSAGE;
- WebimEvents.EDIT_MESSAGE;
- WebimEvents.CLEAR_DIALOG;
- WebimEvents.TOKEN_UPDATED;
- WebimEvents.ERROR;
- WebimEvents.STATE;
- WebimEvents.UNREAD_COUNTER;
- WebimEvents.TYPING;
- ~~WebimEvents.FILE_UPLOADING_PROGRESS;~~
As you called `getAllMessages` after that you should call `nextMessages` as reading "all messages" during the same session will get no result (native implementation uses holder and cursor by last loaded message)
```js
const { messages } = await RNWebim.getLastMessages(limit);
// or
const { messages } = await RNWebim.getNextMessages(limit);
// or
const { messages } = await RNWebim.getAllMessages();
```
**Message type**
```typescript
export type WebimMessage = {
id: string;
serverSideId: string;
avatar?: string;
time: number;
type: MessageTypeAlias; // 'OPERATOR', 'VISITOR', 'INFO', 'ACTION_REQUEST', 'CONTACTS_REQUEST', 'FILE_FROM_OPERATOR', 'FILE_FROM_VISITOR', 'OPERATOR_BUSY', 'KEYBOARD', 'KEYBOARD_RESPONSE';
text: string;
name: string;
status: 'SENT' | 'SENDING';
read: boolean;
canEdit: boolean;
carReply: boolean;
isEdited: boolean;
canReact: boolean;
canChangeReaction: boolean;
visitorReaction?: string;
stickerId?: number;
quote?: Quote;
attachment?: WebimAttachment;
operatorId?: string;
}
```
**Quote type**
```typescript
export type Quote = {
authorId?: string;
senderName: string;
messageId: string;
messageText: string;
messageType: MessageTypeAlias;
state: 'FILLED' | 'NOT_FOUND' | 'PENDING';
timestamp: Date | number;
attachment?: WebimAttachment;
};
```
**Included attachment**
```typescript
export interface WebimAttachment {
contentType: string;
info: string;
name: string;
size: number;
url: string;
}
```
Note: method `getAllMessages` works strange on iOS, and sometimes returns empty array. We recommend to use `getLastMessages` instead
```typescript
import RNWebim from 'rn-webim-chat';
const messageId = await RNWebim.send(message);
```
You can manually mark all messages as read by calling this method.
```typescript
import RNWebim from 'rn-webim-chat';
await RNWebim.readMessages();
```
In future will add possibility to use external library as `react-native-fs` and some other picker to import files via them.
For now there are such methods
```typescript
var result: AttachFileResult = await RNWebim.tryAttachAndSendFile();
console.log('uri: ', result.uri)
console.log('name: ', result.name)
console.log('mime: ', result.mime)
console.log('extension: ', result.extension)
```
```typescript
import RNWebim from 'rn-webim-chat';
try {
RNWebim.sendFile(uri, name, mime, extension)
console.log('Result: ', sendingResult.id)
} catch (e) {
// can throw such errors
'FILE_SIZE_EXCEEDED', 'FILE_SIZE_TOO_SMALL', 'FILE_TYPE_NOT_ALLOWED', 'MAX_FILES_COUNT_PER_CHAT_EXCEEDED', 'UPLOADED_FILE_NOT_FOUND', 'UNAUTHORIZED',
}
```
```typescript
const onSelectFiles = useCallback(async () => {
try {
const fileResult = await RNWebim.tryAttachAndSendFile();
console.log('File result: ', fileResult);
} catch (err: any) {
const webimError = err as WebimNativeError;
console.log('Chat][File] error: ', webimError);
if (webimError.errorType === 'common') {
setNotFatalError(
webimError.message + `(Code: ${webimError.errorCode})`
);
} else {
setFatalError(webimError.message + `(Code: ${webimError.errorCode})`);
}
}
}, []);
```
```js
RNWebim.rateOperator(rate: number)
```
- `rate` (required) - is number from 1 to 5
```typescript
import RNWebim from 'rn-webim-chat';
RNWebim.getCurrentOperator()
```
it returns such object
```typescript
export type Operator = {
id: string;
name: string;
avatar?: string;
title: string;
info: string;
};
```
```js
RNWebim.destroySession(clearData);
```
- clearData (optional) boolean - If true wil
**Tl;DR;**
You have to generate private key in your Webim Account and kinda sign your user fields values.
For more details see [webim documentation](https://webim.ru/kb/dev/identification/id-2-0.html) for client identification.
in [Example app](./example) there is code how to achieve it.
Example:
I'd recommend to you use some lightweight library. HMAC-256 is enough. Actually you can use md5 algorithm - but I'd avoid it.
There are some other aproches e.g. with JsCrypto or with [react-native-crypto ](https://github.com/tradle/react-native-crypto). But here you need to hash all your modules.
Like [here](https://github.com/volga-volga/react-native-webim#start-chat-with-user-data). But the choice it is up to you!
- install [js-sha256](https://github.com/emn178/js-sha256)
- write hash-function to sign your fields.
- use it in your app.
```ts
// chat-utils.ts file
import { sha256 } from 'js-sha256';
const getHmac_sha256 = async (str: string, privateKey: string) => {
return sha256.hmac(privateKey, str);
};
/**
* Returns hash value for authorized user.
* @param obj - User's json fields.
* @param privateKey - private key value. By that hash will be generated.
*/
export const getHashForChatSign = async (
obj: { [key: string]: string },
privateKey: string
) => {
const keys = Object.keys(obj).sort();
const str = keys.map((key) => obj[key]).join('');
return await getHmac_sha256(str, privateKey);
};
```
```tsx
// App.tsx file
...
import { getHashForChatSign } from './chat-utils';
const PRIVATE_KEY = 'YOUR-PRIVATE-KEY-FROM-PORTAL';
const CHAT_SERVICE_ACCOUNT = 'YOU-ACCOUNT';
const acc = {
fields: {
id: 'some-id',
display_name: '1.0.0',
phone: '+79000000000',
address: 'Tomsk',
},
hash: '',
};
async function intSession() {
acc.hash = await getHashForChatSign(acc.fields, PRIVATE_KEY);
const sessionsParams = {
accountName: CHAT_SERVICE_ACCOUNT,
location: '',
storeHistoryLocally: true,
accountJSON: JSON.stringify(acc),
appVersion: AppConfig.version,
clearVisitorData: true,
};
await RNWebim.resumeSession(sessionsParams);
console.log('[Chat][Init] initialized with params: ', sessionsParams);
};
...
```
See the [contributing guide](CONTRIBUTING.md) guide to learn how to contribute to the repository and the development workflow.
Software provided as it is.
It will be maintained time-to-time. Currently, I have to use this package in some applications, so I try to keep it on working.
If you want to help or improve something see section
---
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)