diff --git a/README.md b/README.md index b02582c..e52c3ce 100644 --- a/README.md +++ b/README.md @@ -463,8 +463,63 @@ router.post('/webhook/event', lark.adaptKoaRouter(eventDispatcher)); server.use(router.routes()); server.listen(3000); ```` +#### Combined with NextJS +```typescript +// pages/api/webhook.ts +import * as lark from '@larksuiteoapi/node-sdk'; + +const client = new lark.Client({ + appId: 'xxxxxxxxxxxxxxxxxxxxxxx', + appSecret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', + appType: lark.AppType.SelfBuild +}); + +const eventDispatcher = new lark.EventDispatcher({ + verificationToken: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', + encryptKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', +}).register({ + 'im.message.receive_v1': async (data) => { + const chatId = data.message.chat_id; + + const res = await client.im.message.create({ + params: { + receive_id_type: 'chat_id', + }, + data: { + receive_id: chatId, + content: JSON.stringify({text: 'hello world'}), + msg_type: 'text' + }, + }); + return res; + } +}); + +export default async (req: NextApiRequest, res: NextApiResponse) => { + await lark.adaptNextjs(eventDispatcher, { + autoChallenge: true, + })(req, res); +}; +``` + #### Custom adapter -If you want to adapt to services written by other libraries, you currently need to encapsulate the corresponding adapter yourself. Pass the received event data to the invoke method of the instantiated `eventDispatcher` for event processing: +If you need to adapt a service written in another library, you can refer to the following approach to call the encapsulated custom adapter. + +```typescript +// Taking NextJS as an example: +export default async (req: NextApiRequest, res: NextApiResponse) => { + const result = await lark.adaptCustom(eventDispatcher, { + autoChallenge: true, + })(req.headers, req.body); + res.end(result); +}; +``` + +If the call fails or an error occurs, please check if the req.headers and req.body formats match the following image by setting breakpoints: + +![](doc/request-body.png) + +If you need to encapsulate it yourself, you can refer to the following logic. Pass the received event data to the invoke method of the instantiated `eventDispatcher` for event processing: ```typescript const data = server.getData(); diff --git a/README.zh.md b/README.zh.md index 311da2c..098cf66 100644 --- a/README.zh.md +++ b/README.zh.md @@ -463,10 +463,68 @@ router.post('/webhook/event', lark.adaptKoaRouter(eventDispatcher)); server.use(router.routes()); server.listen(3000); ``` +#### 和NextJS结合 + +```typescript +// pages/api/webhook.ts +import * as lark from '@larksuiteoapi/node-sdk'; + +const client = new lark.Client({ + appId: 'xxxxxxxxxxxxxxxxxxxxxxx', + appSecret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', + appType: lark.AppType.SelfBuild +}); + +const eventDispatcher = new lark.EventDispatcher({ + verificationToken: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', + encryptKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', +}).register({ + 'im.message.receive_v1': async (data) => { + const chatId = data.message.chat_id; + + const res = await client.im.message.create({ + params: { + receive_id_type: 'chat_id', + }, + data: { + receive_id: chatId, + content: JSON.stringify({text: 'hello world'}), + msg_type: 'text' + }, + }); + return res; + } +}); + +export default async (req: NextApiRequest, res: NextApiResponse) => { + await lark.adaptNextjs(eventDispatcher, { + autoChallenge: true, + })(req, res); +}; +``` + #### 自定义适配器 -如果要适配其它库编写的服务,目前需要自己来封装相应的适配器。将接收到的事件数据传递给实例化的`eventDispatcher`的invoke方法进行事件的处理即可: + +如果要适配其它库编写的服务,可以参考下述方式来调用已经封装的自定义适配器 + +```typescript +// 以 NextJS 为例 +export default async (req: NextApiRequest, res: NextApiResponse) => { + const result = await lark.adaptCustom(eventDispatcher, { + autoChallenge: true, + })(req.headers, req.body); + res.end(result); +}; +``` + +如果调用失败或者错误,请断点检查 `req.headers` 和 `req.body` 格式是否和下图中一致 +![](doc/request-body.png) + + +如果需要自行封装,可参考下述逻辑,将接收到的事件数据传递给实例化的 `eventDispatcher` 的 invoke 方法进行事件的处理即可: ```typescript +// 注意这是伪代码 const data = server.getData(); const result = await dispatcher.invoke(data); server.sendResult(result); diff --git a/adaptor/custom.ts b/adaptor/custom.ts new file mode 100644 index 0000000..c9019f4 --- /dev/null +++ b/adaptor/custom.ts @@ -0,0 +1,43 @@ +import get from 'lodash.get'; +import { EventDispatcher } from '@node-sdk/dispatcher/event'; +import { CardActionHandler } from '@node-sdk/dispatcher/card'; +import { generateChallenge } from './services/challenge'; + +export const adaptCustom = + ( + dispatcher: EventDispatcher | CardActionHandler, + options?: { + autoChallenge?: boolean; + } + ) => + async (headers, body) => { + if (!body || !headers) { + return; + } + + const data = Object.assign( + Object.create({ + headers: headers, + }), + body + ); + + const autoChallenge = get(options, 'autoChallenge', false); + if (autoChallenge) { + const { isChallenge, challenge } = generateChallenge(data, { + encryptKey: dispatcher.encryptKey, + }); + + if (isChallenge) { + return JSON.stringify(challenge); + } + } + + const value = await dispatcher.invoke(data); + + // event don't need response + if (dispatcher instanceof CardActionHandler) { + return JSON.stringify(value); + } + return ''; + }; \ No newline at end of file diff --git a/adaptor/nextjs.ts b/adaptor/nextjs.ts new file mode 100644 index 0000000..3382e1c --- /dev/null +++ b/adaptor/nextjs.ts @@ -0,0 +1,22 @@ +import get from 'lodash.get'; +import { EventDispatcher } from '@node-sdk/dispatcher/event'; +import { CardActionHandler } from '@node-sdk/dispatcher/card'; +import { adaptCustom } from './custom'; + +export const adaptNextjs = + ( + dispatcher: EventDispatcher | CardActionHandler, + options?: { + autoChallenge?: boolean; + } + ) => + async (req, res) => { + if (!req?.body || !req?.headers) { + return; + } + res.end( + await adaptCustom(dispatcher, { + autoChallenge: get(options, 'autoChallenge', false), + })(req.headers, req.body) + ); + }; \ No newline at end of file diff --git a/doc/request-body.png b/doc/request-body.png new file mode 100644 index 0000000..a68cee3 Binary files /dev/null and b/doc/request-body.png differ