mirror of https://github.com/msgbyte/tailchat
feat(server): add getuiclient lib and plugin
parent
82a08d65ef
commit
1605b2d186
@ -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…
Reference in New Issue