
# 云开发 WebSocketIO API 文档

## API 列表

### IO API

#### 初始化 IO

##### 使用默认配置

```javascript
const { FaasWebSocketIO } = require('@alipay/faas-biz-server-sdk');
// 初始化 FaasWebSocketIO，默认使用 MongoDB 作为存储
const wsio = new FaasWebSocketIO();

```

##### redis 配置

```javascript
// 初始化 FaasWebSocketIO，使用 Redis 作为存储
const wsio = new FaasWebSocketIO({ 
  storage: {
    type: 'redis',
  }
});

// 初始化 FaasWebSocketIO，使用 Redis 作为存储，指定 Redis 配置
const wsio = new FaasWebSocketIO({ 
  storage: {
    type: 'redis',
    // 参考 ioredis 配置
    config: {
      host: 'myhost',
      port: 6379,
      password: 'mypassword',
    }
  }
});

```

#### of 指定 namespace

可以使用 namespace 对 room 进行组织，默认 namespace 为 `/`。

```javascript
await wsio.of('/namespace').send('data');
```

#### 筛选 room

##### 指定 to/in

筛选 room，可以使用 `to` 或 `in` 方法，两者等价。

```javascript

await wsio.to('room1').send('data');
await wsio.in('room1').send('data');

await wsio.of('/namespace').to('room1').send('data');

```

##### 排除 except

排除 room，可以使用 `except` 方法。

```javascript

await wsio.of('/namespace').except('room1').send('data');
await wsio.of('/namespace').except([ 'room1', 'room2' ]).send('data');

```

##### 获取所有 room

获取所有 room，可以使用 `rooms`。

```javascript

await wsio.rooms
await wsio.of('/namespace').rooms

```

#### 筛选连接

```javascript
await wsio.to('room1').sockets;
await wsio.of('/namespace').to('room1').sockets;
await wsio.sockets;
```

#### 发送消息

##### 发送

```javascript

await wsio.send('data');
await wsio.of('/namespace').send('data');
await wsio.to('room1').send('data');

```

##### 广播

给除自己外的所有筛选出来的连接发送消息。为了确定当前连接，需要在wsio初始化时将当前连接的connectionId作为参数传入。

```javascript

const wsio = new FaasWebSocketIO({ connectionId: event.connectionId });

await wsio.broadcast.send('data');
await wsio.of('/namespace').broadcast.send('data');
await wsio.to('room1').broadcast.send('data');

```

#### 删除连接

删除指定连接在数据库中保存的信息, 但不会关闭连接,一般用于清理无效连接
可以配合`websocket:disconnect`事件使用

```javascript

// 主动删除连接信息
await wsio.delete();
await wsio.of('/namespace').delete();
await wsio.in('room1').delete();

```

```javascript

// 在 websocket:disconnect 事件中使用
addEventListener('websocket:disconnect', event => {
  event.handle((async () => {
    await wsio.delete();
    // 或者
    await wsio.of('/namespace').delete();
    // 或者
    await wsio.in('room1').delete();
    // 或者
    (await wsio.sockets).forEach(async socket => {
      await socket.delete();
    });
  })());
});

```

#### 关闭连接

主动关闭筛选出来的连接。同时删除数据库中保存的连接信息
注意，调用这个 `close()` 之后所有相关的连接都会被关闭

```typescript
wsio.close(closeOptions?: { 
  // 关闭状态码
  status?: CloseStatus, 
  // 是否不忽略连接存活状态，如果为 true，则会检查连接是否存活，如果存活则正常关闭连接
  // 如果为 false 或者不设值，则检查连接是否存活，如果不存活，则会抛出错误, 且中断关闭操作
  strict?: boolean 
}): Promise<void>
```

```javascript

// 关闭默认 namespace 下的所有连接
await wsio.close();

// 关闭指定 namespace 下的所有连接
await wsio.of('/namespace').close();

// 关闭指定 room 的所有连接
await wsio.in('room1').close();

await wsio.close({
  status: CloseStatus.NORMAL;
});

```

#### socketsJoin

join 操作，将当前筛选出来的连接加入指定 room。

```javascript

await wsio.socketsJoin('room1');
await wsio.of('/namespace').socketsJoin('room1');
await wsio.socketsJoin([ 'room1', 'room2' ]);

```

#### socketsLeave

leave 操作，将当前筛选出来的连接离开指定 room。

```javascript

await wsio.socketsLeave('room1');
await wsio.of('/namespace').socketsLeave('room1');
await wsio.socketsLeave([ 'room1', 'room2' ]);

```

#### 获取 socket 状态

获取当前筛选出来的连接的状态。

```javascript

await wsio.socketsIsAlive();
await wsio.of('/namespace').socketsIsAlive();
await wsio.to('room1').socketsIsAlive();

/**
 * 返回值示例
  [
    { connectionId: '0A4B4CDC01J0TDY5G92HP3DME3554K7253', alive: true },
    { connectionId: '0A4B078001J0TFRRGZYM63RAGS65TK0CHH', alive: true }
  ] 
 */

```

### Socket API

#### 初始化 Socket

```javascript

const { FaasWebSocketIO } = require('@alipay/faas-biz-server-sdk');
const wsio = new FaasWebSocketIO();

addEventListener('websocket:connect', event => {
  event.handle((async () => {
    console.log('new connection, connId=' + event.connectionId);
    const socket = new wsio.Websocket({ connectionId: event.connectionId });
  })());
});

```

#### socket 发送消息

```javascript

await socket.send('data');

```

#### socket 广播

```javascript

await socket.broadcast.send('data');

```

#### socket 删除连接信息

删除数据库中保存的连接信息，但不会关闭连接,一般用于清理无效连接，可以配合`websocket:disconnect`事件使用

```javascript

// 主动删除连接信息
await socket.delete();

```

```javascript

// 在 websocket:disconnect 事件中使用
addEventListener('websocket:disconnect', event => {
  event.handle((async () => {
    await socket.delete();
  })());
});

```

#### socket 关闭连接

主动关闭连接，同时删除数据库中保存的连接信息，注意，调用 `close()` 之后当前连接会被关闭，不会再接收到任何消息

```typescript
socket.close(closeOptions?: { 
  // 关闭状态码
  status?: CloseStatus, 
  // 如果为 true，则检查连接是否存活，如果不存活，则会抛出错误, 且中断关闭操作
  // 如果为 false 或者不设值，则会检查连接是否存活，如果存活则正常关闭连接，否则删除数据库中的连接信息
  strict?: boolean 
}): Promise<void>
```

```typescript
await socket.close();
await socket.close({
  status: CloseStatus.NORMAL;
});

```

#### leave

```javascript

await socket.leave('room1');

```

#### join

```javascript

await socket.join('room1');

```

#### rooms

获取 socket 所在房间列表

```javascript

await socket.rooms;

```

#### setProperty

设置 socket 属性

```javascript


// 设置单个值
await socket.setProperty('key', 'value');

await socket.setProperty('key1', ['value1', 'value2']);

await socket.setProperty('key2', { key: 'value' });

await socket.setProperty('key3', 1);

await socket.setProperty('key4', new Map([['key', 'value']]));

await socket.setProperty('key5', new Set(['value']));

// 设置多个值
await socket.setProperty('key', 'value', 'key1', 'value1', 'key2', new Map([['key', 'value']]));

```

#### getProperty

获取 socket 属性

```javascript

await socket.getProperty('key'); // 返回 'value'
await socket.getProperty('key', 'key1'); // 返回 { key: 'value', key1: 'value1' }

```

#### properties

获取 socket 所有属性

```javascript

await socket.properties;

```

#### isAlive

获取 socket 是否存活

```javascript

await socket.isAlive;
// true or false

```

### Room API

#### 设置 room 属性

```javascript
// 设置单个值
await room.setProperty('key', 'value');
await room.setProperty('key1', ['value1', 'value2']);
await room.setProperty('key2', { key: 'value' });
await room.setProperty('key3', 1);
await room.setProperty('key4', new Map([['key', 'value']]));
await room.setProperty('key5', new Set(['value']));

// 设置多个值
await room.setProperty('key', 'value', 'key1', 'value1', 'key2', new Map([['key', 'value']]));

```

#### 获取 room 属性

```javascript

await room.getProperty('key'); // 返回 'value'
await room.getProperty('key', 'key1'); // 返回 { key: 'value', key1: 'value1' }

```

#### properties

获取 room 所有属性

```javascript

await room.properties;

```

#### sockets

获取 room 所有连接

```javascript
await room.sockets;
```

#### members

获取 room 所有连接

```javascript
await room.members;
```

#### socketsIsAlive

获取 room 所有连接的存活状态

```javascript
await room.socketsIsAlive();
```

#### join

向 room 中加入连接

```javascript
await room.join(socket); // socket 为 FaasWebSocketIO.Websocket 实例
```

#### leave

从 room 中移除连接

```javascript
await room.leave(socket); // socket 为 FaasWebSocketIO.Websocket 实例
```

#### broadcast

向 room 中的所有连接广播消息

```javascript

await room.broadcast('data');

```

## 示例

```js
const { FaasWebSocketIO } = require('@alipay/faas-biz-server-sdk');
const wsio = new FaasWebSocketIO();

addEventListener('websocket:connect', event =>{
  event.handle((async () => {
    console.log('new connection, connId=' + event.connectionId);
    const socket = new wsio.Websocket({connectionId: event.connectionId});
    await socket.join('room1');
    const a = new Map();
    a.set('key1', 'value1');
    await socket.setProperty('name', a);
  })());
});
addEventListener('websocket:message', event =>{
  event.handle((async () => {
    console.log('new message, connId=' + event.connectionId);
    const socket = new wsio.Websocket({ connectionId: event.connectionId});
    await socket.join('room2');
    const customMap = await socket.getProperty('name');
    console.log('customMap', customMap)
    await wsio.to('room1').to('room2').send('echo: ' + event.payload + ', from: ' + (customMap? customMap.get('key1') : 'ssss'));
    await wsio.to('room1').to('room2').except('room2').send('echo1: ' + event.payload + ', from: ' + (customMap? customMap.get('key1') : 'ssss'));
    await wsio.to('room1').to('room2').except('room1').broadcast.send('echo2: ' + event.payload + ', from: ' + (customMap? customMap.get('key1') : 'ssss'));
    console.log('rooms:', await wsio.rooms);
    console.log('name:', await socket.getProperty('name'));
    const a = await wsio.to('room1').sockets;
    console.log('room1 members:', a.size);
    await wsio.to('room1').send('room list:' + JSON.stringify(await wsio.rooms));
    const rooms1 = await wsio.of('/').rooms;
    console.log('rooms/', rooms1.size);
    await wsio.of('/').send('rooms/:' + rooms1.size);
    if (event.payload === 'leave2') {
      await socket.leave('room2');
    }
    if (event.payload === 'close') {
      await socket.close();
    }
    if (event.payload === 'delete') {
      await socket.delete();
    }

    const allSocketsAlive = await wsio.socketsIsAlive();
    const room1SocketsAlive = await wsio.to('room1').socketsIsAlive();
    const socketAlive = await socket.isAlive;
    console.log('allSocketsAlive:', allSocketsAlive);
    console.log('room1SocketsAlive:', room1SocketsAlive);
    console.log('socketAlive:', socketAlive);
  })());
});
addEventListener('websocket:disconnect', event =>{
  event.handle((async () => {
    console.log('disconnect, connId=' + event.connectionId);
    const socket = new wsio.Websocket({ connectionId: event.connectionId });
    await socket.delete();
  })());
});
addEventListener('websocket:error', event =>{
  event.handle((async () => {
    console.log('error, connId=' + event.connectionId);
    console.log('event:' + JSON.stringify(event));
  })());
});

```

## 枚举

```typescript

enum CloseStatus {
  NORMAL = 'NORMAL', // indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled.
  GOING_AWAY = 'GOING_AWAY', // indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page.
  PROTOCOL_ERROR = 'PROTOCOL_ERROR', // indicates that an endpoint is terminating the connection due to a protocol error.
  BAD_DATA = 'BAD_DATA', // indicates that an endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the message (e.g., non-UTF-8 [RFC3629] data within a text message).
  TOO_BIG_TO_PROCESS = 'TOO_BIG_TO_PROCESS', // indicates that an endpoint is terminating the connection because it has received a message that is too big for it to process.
  SERVER_ERROR = 'SERVER_ERROR', // indicates that a server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.
  SERVICE_RESTARTED = 'SERVICE_RESTARTED', // indicates that the service is restarted. A client may reconnect, and if it chooses to do, should reconnect using a randomized delay of 5 - 30s.
  SERVICE_OVERLOAD = 'SERVICE_OVERLOAD', // indicates that the service is experiencing overload. A client should only connect to a different IP (when there are multiple for the target) or reconnect to the same IP upon user action.
  USER_STATUS_1 = 'USER_STATUS_1', // custom status for user application
  USER_STATUS_2 = 'USER_STATUS_2', // custom status for user application
  USER_STATUS_3 = 'USER_STATUS_3', // custom status for user application
}

enum WSStorageType {
  MONGODB = 'mongodb', // 使用 MongoDB 作为存储
  REDIS = 'redis', // 使用 Redis 作为存储
}

```

### CloseStatus

| status            | 说明                                                                                                                                                   |
|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| NORMAL            | indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled.                                      |
| GOING_AWAY        | indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page.                                |
| PROTOCOL_ERROR    | indicates that an endpoint is terminating the connection due to a protocol error.                                                                       |
| BAD_DATA          | indicates that an endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the message (e.g., non-UTF-8 [RFC3629] data within a text message). |
| TOO_BIG_TO_PROCESS| indicates that an endpoint is terminating the connection because it has received a message that is too big for it to process.                          |
| SERVER_ERROR      | indicates that a server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.     |
| SERVICE_RESTARTED | indicates that the service is restarted. A client may reconnect, and if it chooses to do, should reconnect using a randomized delay of 5 - 30s.         |
| SERVICE_OVERLOAD  | indicates that the service is experiencing overload. A client should only connect to a different IP (when there are multiple for the target) or reconnect to the same IP upon user action. |
| USER_STATUS_1     | custom status for user application                                                                                                                     |
| USER_STATUS_2     | custom status for user application                                                                                                                     |
| USER_STATUS_3     | custom status for user application                                                                                                                     |

### WSStorageType

| type    | 说明                                                                 |
|---------|----------------------------------------------------------------------|
| MONGODB | 使用 MongoDB 作为存储                                                  |
| REDIS   | 使用 Redis 作为存储                                                    |
