65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
import { Inject, Injectable } from '@nestjs/common';
|
|
import type { NotificationType } from '@lehuo/contracts';
|
|
import { AppConfigService } from '../config/app-config.service';
|
|
export interface SubscriptionMessage {
|
|
openid: string;
|
|
type: NotificationType;
|
|
title: string;
|
|
body: string;
|
|
resourcePublicId: string | null;
|
|
}
|
|
export interface SubscriptionSendResult {
|
|
providerMessageId: string | null;
|
|
}
|
|
export abstract class WechatSubscriptionAdapter {
|
|
abstract get configured(): boolean;
|
|
abstract send(message: SubscriptionMessage): Promise<SubscriptionSendResult>;
|
|
}
|
|
@Injectable()
|
|
export class HttpWechatSubscriptionAdapter extends WechatSubscriptionAdapter {
|
|
constructor(@Inject(AppConfigService) private readonly config: AppConfigService) {
|
|
super();
|
|
}
|
|
get configured() {
|
|
return this.config.wechatNotificationConfigured;
|
|
}
|
|
async send(message: SubscriptionMessage) {
|
|
if (!this.config.wechatAppId || !this.config.wechatAppSecret)
|
|
throw new Error('WECHAT_NOT_CONFIGURED');
|
|
const templateId = this.config.wechatNotificationTemplates[message.type];
|
|
if (!templateId) throw new Error('WECHAT_TEMPLATE_NOT_CONFIGURED');
|
|
const tokenResponse = await fetch(
|
|
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${encodeURIComponent(this.config.wechatAppId)}&secret=${encodeURIComponent(this.config.wechatAppSecret)}`,
|
|
);
|
|
const token = (await tokenResponse.json()) as { access_token?: string; errcode?: number };
|
|
if (!tokenResponse.ok || !token.access_token)
|
|
throw new Error(`WECHAT_TOKEN_${token.errcode ?? 'FAILED'}`);
|
|
const response = await fetch(
|
|
`https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=${encodeURIComponent(token.access_token)}`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({
|
|
touser: message.openid,
|
|
template_id: templateId,
|
|
page: message.resourcePublicId
|
|
? `pages/index/index?resource=${message.resourcePublicId}`
|
|
: 'pages/index/index',
|
|
data: {
|
|
thing1: { value: message.title.slice(0, 20) },
|
|
thing2: { value: message.body.slice(0, 20) },
|
|
},
|
|
}),
|
|
},
|
|
);
|
|
const result = (await response.json()) as {
|
|
errcode?: number;
|
|
errmsg?: string;
|
|
msgid?: string | number;
|
|
};
|
|
if (!response.ok || result.errcode !== 0)
|
|
throw new Error(`WECHAT_SEND_${result.errcode ?? 'FAILED'}`);
|
|
return { providerMessageId: result.msgid?.toString() ?? null };
|
|
}
|
|
}
|