## IM
Lightweight, extensible, JavaScript Instant Messaging.

### 功能
1. 异常断开时，内部实现自动重连（下面几种方式即可无限重连）的情况：
   1. 服务端断网
   2. 服务端重启
   3. 客户端断网
2. 不会自动重连的情况：
   1. 客户端主动断线（im.disconnect()）
   2. 服务端主动断线（需要服务端发送一个断开连接的标识(前后端约定)，在 MESSAG 监听到后，调用im.disconnect()）
3. 心跳机制。
4. 监听客户端的连接状态
5. 设置ping间隔
6. 设置userId
7. 区分会话类型（在发送消息时确定）
   1. server：0         server <-> user
   2. 单聊：1           user1  <-> user2
8. 消息发送（默认时server会话类型）
   1. 文字消息
   2. 图片消息
   3. 文件消息
   4. ping消息（内置）
   5. 信令消息（在与服务交互时使用）
9. 监听
   1. 消息监听
   2. 连接状态监听
10. 信令消息的库扩展性（客户端和服务端可根据约定signalName实现业务层逻辑）
11. 透传服务端发送的消息（content的消息透传）


### 注意事项
1. 客户端主动发送消息，服务端收到消息并返回，从而形成一个闭环，才能证明客户端发送消息成功了。通过messageId才能形成闭环，也就是在客户端主动发消息情况下，服务端需返回客户端携带的messageId。客户端在send回调中拿到消息。
2. 服务端主动给客户端发送消息不用携带messageId，客户端在消息监听中获取到消息。
3. 需要在连接成功(监听 STATUS 为表示连接成功)后，才能发送消息、监听消息。

### Usage

#### NPM、AMD and CommonJS module
参见集成客户端 demo（需注意：当前没有连接服务端）({your app path}/node_modules/awesome-im/src/example/client.html).

```js

// 静态文件引入 awesome-im
// <script src="${path}awesome-im/dist/index.umd.js"></script>

// npm 安装、引入
// npm i awesome-im@latest
// import im from "awesome-im"


// websocket 地址
const url = "ws://localhost:8088";

// 初始化
im.init({
   pingGap: 6000,
   userId: "allenye" // 自定义用户ID
});

// 连接
im.connect(url).then(res => {
   console.log("connect ->", res)
})

// 监听连接状态
im.addEventListener("STATUS", evt => {
   console.log("连接状态->", evt)
})

// 监听消息
im.addEventListener("MESSAGE", evt => {
   console.log("监听消息->", evt)
})

// 发送信令消息
im.send(new im.Message.SignalMessage({
    signalName: "start", // 信令名称自定义，与服务端约定。
    // signalName: "end",
    to: "server"
})).then(res => {
    console.log("发送SignalMessage成功", res)
})

// 发送文字消息
im.send(new im.Message.TextMessage({
    // message: "message 可以扩展"
    message: {
        test: 123,
        array: [123, 456],
        object: {
            name: "allen"
        }
    },
    from: "allen",
    to: "server"
})).then(res => {
    console.log("发送TextMessage成功 ->", res)
})

// 修改会话类型(需要设置 conversationType),需要注意需要服务处理 user1、user2 之间的通信。
// 默认时server会话（im.Message.ConversationType.SERVER）
im.send(new im.Message.TextMessage({
    conversationType: im.Message.ConversationType.PRIVATE, // 单聊会话
    message: "发送一条单聊会话消息",
    from: "user1",
    to: "user2"
})).then(res => {
    console.log("发送TextMessage成功 ->", res)
})


```

### 服务端 demo

参见集成服务端nodejs demo ({your app path}/node_modules/awesome-im/src/example/server.js).

```js

// nodejs
const WebSocket = require('ws');
const { encode, decode } = require("@msgpack/msgpack");

const server = new WebSocket.Server({ port: 8088 });

server.on('connection', (socket) => {
    console.log('Client connected');
    // setInterval(() => {
    //     server.clients.forEach((client) => {
    //         client.send(encode({
    //             code: ErrorCode.SUCCESS,
    //             data: {
    //                 messageType: MessageType.SIGNAL,
    //                 sentTime: new Date().getTime(),
    //                 messageUId: 'CBE5-1922-F8C9-730B',
    //                 conversationType: ConversationType.SERVER,
    //                 to: 'allen',
    //                 signalName: 'end',
    //                 from: 'server',
    //                 content: {
    //                     message: "一条服务端发送的消息"
    //                 },
    //             },
    //             errMsg: "success!"
    //         }));
    //     })
    // }, 2000);

    // 当接收到消息时，向所有连接的客户端广播消息
    socket.on('message', (message) => {
        // console.log(`Received message: ${message}`);
        const _data = decode(message)
        server.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
                const data = {
                    ..._data,
                    time: new Date().getTime()
                }
                console.log(data)
                client.send(encode({
                    code: ErrorCode.SUCCESS,
                    data: data
                }));
            }
        });
    });

    // 当连接关闭时，向所有连接的客户端广播消息
    socket.on('close', () => {
        console.log('Client disconnected');
        server.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(encode({ errMsg: "close", code: 4 }));
            }
        });
    });
});
```

#### 服务返回数据格式
服务端主动发送消息,不用携带 messageId。
```json
{
    "code": 0,
    "data": {
        "messageType": "SignalMsg",
        "sentTime": 1681900543414,
        "messageUId": "CBE5-1922-F8C9-730B",
        "conversationType": 0,
        "to": "allen",
        "signalName": "login", // 可扩展
        "from": "server",
        "messageDirection": 2
    },
    "errMsg": "success!"
}
```
服务端收到客户端发送的消息后（服务端发送闭环消息），需要携带messageId
```json
{
    "code": 0,
    "data": {
        "messageType": "TextMsg",
        "sentTime": 1681900543414,
        "messageId": "**需要携带客户端发送消息的messageId，从而达到闭环。**",
        "messageUId": "CBE5-1922-F8C9-730B",
        "conversationType": 0,
        "to": "allen",
        "signalName": "end", // 可扩展
        "from": "server",
        "content": {
            // 可以扩展
            "message": "客户端发送消息后，服务端需要返回一条消息（闭环）。证明客户端发送的消息成功了。"
        },
        "messageDirection": 2
    },
    "errMsg": "success!"
}
```

### 消息、会话相关枚举数据
```js
// 消息类型
export const MessageType = {
    TEXT: "TxtMsg",
    IMAGE: "ImgMsg",
    FILE: "FileMsg",
    PING: "PingMsg",
    SIGNAL: "SignalMsg"
}

// 会话类型
export const ConversationType = {
    /**
     * 客户端与服务端的会话
     */
    SERVER: 0,
    /**
     * 单聊
     */
    PRIVATE: 1,
    /**
     * 讨论组
     */
    DISCUSSION: 2,
    /**
     * 群组聊天
     */
    GROUP: 3,
    /**
     * 聊天室会话
     */
    CHATROOM: 4,
    /**
     * 系统消息
     */
    SYSTEM: 5,
}
// 消息方向
export const MessageDirection = {
    /**
     * 发送消息。
     */
    SEND: 1,
    /**
     * 接收消息。
     */
    RECEIVE: 2
}

// 事件监听
const event = {
    STATUS: "STATUS",
    MESSAGE: "MESSAGE",
    TEST: "TEST",
}
```