feat(server): add getuiclient lib and plugin

pull/90/head
moonrailgun 2 years ago
parent 82a08d65ef
commit 1605b2d186

@ -1239,6 +1239,19 @@ importers:
styled-components: 5.3.6_react@18.2.0
zustand: 4.1.5_react@18.2.0
server/plugins/com.msgbyte.getui:
specifiers:
'@types/lodash': ^4.14.191
got: ^11.8.3
lodash: ^4.17.21
tailchat-server-sdk: '*'
dependencies:
got: 11.8.3
lodash: 4.17.21
tailchat-server-sdk: link:../../packages/sdk
devDependencies:
'@types/lodash': 4.14.191
server/plugins/com.msgbyte.github:
specifiers:
'@octokit/webhooks-types': ^5.4.0
@ -17740,7 +17753,7 @@ packages:
dependencies:
clone-response: 1.0.3
get-stream: 5.2.0
http-cache-semantics: 4.1.0
http-cache-semantics: 4.1.1
keyv: 4.5.2
lowercase-keys: 2.0.0
normalize-url: 6.1.0
@ -23441,6 +23454,23 @@ packages:
responselike: 3.0.0
dev: true
/got/12.6.0:
resolution: {integrity: sha512-WTcaQ963xV97MN3x0/CbAriXFZcXCfgxVp91I+Ze6pawQOa7SgzwSx2zIJJsX+kTajMnVs0xcFD1TxZKFqhdnQ==}
engines: {node: '>=14.16'}
dependencies:
'@sindresorhus/is': 5.3.0
'@szmarczak/http-timer': 5.0.1
cacheable-lookup: 7.0.0
cacheable-request: 10.2.8
decompress-response: 6.0.0
form-data-encoder: 2.1.4
get-stream: 6.0.1
http2-wrapper: 2.2.0
lowercase-keys: 3.0.0
p-cancelable: 3.0.0
responselike: 3.0.0
dev: true
/got/8.3.2:
resolution: {integrity: sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==}
engines: {node: '>=4'}
@ -24027,9 +24057,6 @@ packages:
resolution: {integrity: sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==}
dev: true
/http-cache-semantics/4.1.0:
resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==}
/http-cache-semantics/4.1.1:
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
@ -29952,7 +29979,7 @@ packages:
resolution: {integrity: sha512-hySwcV8RAWeAfPsXb9/HGSPn8lwDnv6fabH+obUZKX169QknRkRhPxd1yMubpKDskLFATkl3jHpNtVtDPFA0Wg==}
engines: {node: '>=14.16'}
dependencies:
got: 12.5.3
got: 12.6.0
registry-auth-token: 5.0.1
registry-url: 6.0.1
semver: 7.3.8

@ -28,3 +28,8 @@ TAILCHAT_MEETING_URL=
# Admin 后台密码
ADMIN_PASS=com.msgbyte.tailchat
# GETUI Push
GETUI_APPID=
GETUI_APPKEY=
GETUI_MASTERSECRET=

@ -0,0 +1,167 @@
import got from 'got';
import crypto from 'crypto';
import _ from 'lodash';
function sha256(data: string) {
return crypto.createHash('sha256').update(data).digest('hex');
}
interface AuthResPayload {
msg: string;
code: number;
data: {
expire_time: string;
token: string;
};
}
interface SinglePushResPayload {
msg: string;
code: number;
data: {
[taskid: string]: {
[cid: string]:
| 'successed_offline'
| 'successed_online'
| 'successed_ignore';
};
};
}
interface AllPushResPayload {
msg: string;
code: number;
data: {
[taskid: string]: string;
};
}
export class GetuiClient {
token: string;
expireTime: number;
constructor(
public appId: string,
public appkey: string,
public mastersecret: string
) {}
get baseUrl() {
return `https://restapi.getui.com/v2/${this.appId}`;
}
/**
* Generate Request ID with fixed prefix and timestamp and random number
*/
generateRequestId(): string {
return (
'tailchat' +
String(Date.now()) +
_.padStart(String(Math.floor(Math.random() * 1e8)), 8, '0')
);
}
signBody() {
const timestamp = String(Date.now());
return {
sign: sha256(this.appkey + String(Date.now()) + this.mastersecret),
timestamp,
appkey: this.appkey,
};
}
private async fetchToken() {
try {
const res = await got(`${this.baseUrl}/auth`, {
method: 'POST',
headers: {
'content-type': 'application/json;charset=utf-8',
},
json: {
...this.signBody(),
},
}).json<AuthResPayload>();
if (res.code === 0) {
this.token = res.data.token;
this.expireTime = Number(res.data.expire_time) - 60 * 1000; // 提前60s过期
}
} catch (err) {
console.error(err);
}
}
async getToken(): Promise<string> {
if (!this.token || Date.now() > this.expireTime) {
// 如果token不存在或者token过期
await this.fetchToken();
}
return this.token;
}
async singlePush(
userId: string,
title: string,
body: string,
payload: {}
): Promise<SinglePushResPayload> {
const token = await this.getToken();
const requestId = this.generateRequestId();
const res = await got(`${this.baseUrl}/push/single/alias`, {
method: 'POST',
headers: {
'content-type': 'application/json;charset=utf-8',
token,
},
json: {
request_id: requestId,
audience: {
alias: [userId],
},
push_message: {
notification: {
title: title,
body: body,
click_type: 'payload',
payload: JSON.stringify(payload),
},
},
},
}).json<SinglePushResPayload>();
return res;
}
async allPush(
title: string,
body: string,
payload: {}
): Promise<AllPushResPayload> {
const token = await this.getToken();
console.log('token', token);
const requestId = this.generateRequestId();
const res = await got(`${this.baseUrl}/push/all`, {
method: 'POST',
headers: {
'content-type': 'application/json;charset=utf-8',
token,
},
json: {
request_id: requestId,
audience: 'all',
push_message: {
notification: {
title: title,
body: body,
click_type: 'payload',
payload: JSON.stringify(payload),
},
},
},
}).json<AllPushResPayload>();
return res;
}
}

@ -0,0 +1,20 @@
import { db } from 'tailchat-server-sdk';
const { getModelForClass, prop, modelOptions, TimeStamps } = db;
@modelOptions({
options: {
customName: 'p_getui_log',
},
})
export class GetuiLog extends TimeStamps implements db.Base {
_id: db.Types.ObjectId;
id: string;
}
export type GetuiLogDocument = db.DocumentType<GetuiLog>;
const model = getModelForClass(GetuiLog);
export type GetuiLogModel = typeof model;
export default model;

@ -0,0 +1,18 @@
{
"name": "tailchat-plugin-getui",
"version": "1.0.0",
"main": "index.js",
"author": "moonrailgun",
"description": "Support Getui Notify in chinese mainland",
"license": "MIT",
"private": true,
"scripts": {},
"dependencies": {
"got": "^11.8.3",
"lodash": "^4.17.21",
"tailchat-server-sdk": "*"
},
"devDependencies": {
"@types/lodash": "^4.14.191"
}
}

@ -0,0 +1,20 @@
import { TcService, TcDbService } from 'tailchat-server-sdk';
import type { GetuiLogDocument, GetuiLogModel } from '../models/log';
/**
* Support Getui Notify in chinese mainland
*/
interface GetuiService
extends TcService,
TcDbService<GetuiLogDocument, GetuiLogModel> {}
class GetuiService extends TcService {
get serviceName() {
return 'plugin:com.msgbyte.getui';
}
onInit() {
this.registerLocalDb(require('../models/log').default);
}
}
export default GetuiService;

@ -0,0 +1,13 @@
import * as dotenv from 'dotenv';
dotenv.config();
import { GetuiClient } from '../../../plugins/com.msgbyte.getui/lib/GetuiClient';
const client = new GetuiClient(
process.env.GETUI_APPID,
process.env.GETUI_APPKEY,
process.env.GETUI_MASTERSECRET
);
client.allPush('title', 'body', {}).then((res) => {
console.log('res', res);
});
Loading…
Cancel
Save