UNPKG

bottender

Version:

A framework for building conversational user interfaces.

290 lines 12.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const crypto_1 = __importDefault(require("crypto")); const invariant_1 = __importDefault(require("invariant")); const warning_1 = __importDefault(require("warning")); const messaging_api_line_1 = require("messaging-api-line"); const LineContext_1 = __importDefault(require("./LineContext")); const LineEvent_1 = __importDefault(require("./LineEvent")); class LineConnector { constructor(options) { const { getConfig, shouldBatch, sendMethod, skipLegacyProfile, getSessionKeyPrefix, } = options; if ('client' in options) { this._client = options.client; this._channelSecret = options.channelSecret; } else { const { origin } = options; if ('getConfig' in options) { this._getConfig = getConfig; } else { const { accessToken, channelSecret } = options; invariant_1.default(accessToken, 'LINE access token is required. Please make sure you have filled it correctly in your `bottender.config.js` or `.env` file.'); invariant_1.default(channelSecret, 'LINE channel secret is required. Please make sure you have filled it correctly in your `bottender.config.js` or the `.env` file.'); this._client = new messaging_api_line_1.LineClient({ accessToken, channelSecret, origin, }); this._channelSecret = channelSecret; } this._origin = origin; } this._shouldBatch = typeof shouldBatch === 'boolean' ? shouldBatch : true; warning_1.default(!sendMethod || sendMethod === 'reply' || sendMethod === 'push', 'sendMethod should be one of `reply` or `push`'); if (sendMethod) { warning_1.default(false, '`sendMethod` is deprecated. The value will always be `reply` in v2.'); this._sendMethod = sendMethod; } else { this._sendMethod = 'reply'; } this._getSessionKeyPrefix = getSessionKeyPrefix; this._skipLegacyProfile = typeof skipLegacyProfile === 'boolean' ? skipLegacyProfile : true; } _isWebhookVerifyEvent(event) { return ('replyToken' in event && (event.replyToken === '00000000000000000000000000000000' || event.replyToken === 'ffffffffffffffffffffffffffffffff')); } isWebhookVerifyRequest(body) { return (body && Array.isArray(body.events) && body.events.length > 0 && body.events.every(this._isWebhookVerifyEvent)); } get platform() { return 'line'; } get client() { return this._client; } getUniqueSessionKey(bodyOrEvent, requestContext) { const rawEvent = bodyOrEvent instanceof LineEvent_1.default ? bodyOrEvent.rawEvent : bodyOrEvent.events[0]; let prefix = ''; if (this._getSessionKeyPrefix) { const event = bodyOrEvent instanceof LineEvent_1.default ? bodyOrEvent : new LineEvent_1.default(rawEvent, { destination: bodyOrEvent.destination }); prefix = this._getSessionKeyPrefix(event, requestContext); } const { source } = rawEvent; if (source.type === 'user') { return `${prefix}${source.userId}`; } if (source.type === 'group') { return `${prefix}${source.groupId}`; } if (source.type === 'room') { return `${prefix}${source.roomId}`; } throw new TypeError('LineConnector: sender type should be one of user, group, room.'); } updateSession(session, bodyOrEvent) { return __awaiter(this, void 0, void 0, function* () { const rawEvent = bodyOrEvent instanceof LineEvent_1.default ? bodyOrEvent.rawEvent : bodyOrEvent.events[0]; const { source } = rawEvent; if (!session.type) { session.type = source.type; } if (source.type === 'group') { let user = null; if (source.userId) { user = this._skipLegacyProfile || !this._client ? { id: source.userId, _updatedAt: new Date().toISOString(), } : Object.assign({ id: source.userId, _updatedAt: new Date().toISOString() }, (yield this._client.getGroupMemberProfile(source.groupId, source.userId))); } session.user = user; let memberIds = []; try { if (this._client) { memberIds = yield this._client.getAllGroupMemberIds(source.groupId); } } catch (err) { } session.group = { id: source.groupId, members: memberIds.map((id) => ({ id })), _updatedAt: new Date().toISOString(), }; } else if (source.type === 'room') { let user = null; if (source.userId) { user = this._skipLegacyProfile || !this._client ? { id: source.userId, _updatedAt: new Date().toISOString(), } : Object.assign({ id: source.userId, _updatedAt: new Date().toISOString() }, (yield this._client.getRoomMemberProfile(source.roomId, source.userId))); } session.user = user; let memberIds = []; try { if (this._client) { memberIds = yield this._client.getAllRoomMemberIds(source.roomId); } } catch (err) { } session.room = { id: source.roomId, members: memberIds.map((id) => ({ id })), _updatedAt: new Date().toISOString(), }; } else if (source.type === 'user') { if (!session.user) { const user = this._skipLegacyProfile || !this._client ? {} : yield this._client.getUserProfile(source.userId); session.user = Object.assign({ id: source.userId, _updatedAt: new Date().toISOString() }, user); } } if (session.group) { Object.freeze(session.group); } Object.defineProperty(session, 'group', { configurable: false, enumerable: true, writable: false, value: session.group, }); if (session.room) { Object.freeze(session.room); } Object.defineProperty(session, 'room', { configurable: false, enumerable: true, writable: false, value: session.room, }); if (session.user) { Object.freeze(session.user); } Object.defineProperty(session, 'user', { configurable: false, enumerable: true, writable: false, value: session.user, }); }); } mapRequestToEvents(body) { const { destination } = body; return body.events .filter((event) => !this._isWebhookVerifyEvent(event)) .map((event) => new LineEvent_1.default(event, { destination })); } createContext(params) { return __awaiter(this, void 0, void 0, function* () { const { requestContext } = params; let client; if (this._getConfig) { invariant_1.default(requestContext, 'getConfig: `requestContext` is required to execute the function.'); const config = yield this._getConfig({ params: requestContext.params, }); invariant_1.default(config.accessToken, 'getConfig: `accessToken` is missing in the resolved value.'); invariant_1.default(config.channelSecret, 'getConfig: `accessToken` is missing in the resolved value.'); client = new messaging_api_line_1.LineClient({ accessToken: config.accessToken, channelSecret: config.channelSecret, origin: this._origin, }); } else { client = this._client; } return new LineContext_1.default(Object.assign(Object.assign({}, params), { client, shouldBatch: this._shouldBatch, sendMethod: this._sendMethod })); }); } verifySignature(rawBody, signature, { channelSecret }) { const hashBufferFromBody = crypto_1.default .createHmac('sha256', channelSecret) .update(rawBody, 'utf8') .digest(); const bufferFromSignature = Buffer.from(signature, 'base64'); if (bufferFromSignature.length !== hashBufferFromBody.length) { return false; } return crypto_1.default.timingSafeEqual(bufferFromSignature, hashBufferFromBody); } preprocess({ method, headers, rawBody, body, params, }) { return __awaiter(this, void 0, void 0, function* () { if (method.toLowerCase() !== 'post') { return { shouldNext: true, }; } let channelSecret; if (this._getConfig) { const config = yield this._getConfig({ params }); invariant_1.default(config.channelSecret, 'getConfig: `accessToken` is missing in the resolved value.'); channelSecret = config.channelSecret; } else { channelSecret = this._channelSecret; } if (!headers['x-line-signature'] || !this.verifySignature(rawBody, headers['x-line-signature'], { channelSecret, })) { const error = { message: 'LINE Signature Validation Failed!', request: { rawBody, headers: { 'x-line-signature': headers['x-line-signature'], }, }, }; return { shouldNext: false, response: { status: 400, body: { error }, }, }; } if (this.isWebhookVerifyRequest(body)) { return { shouldNext: false, response: { status: 200, body: 'OK', }, }; } return { shouldNext: true, }; }); } } exports.default = LineConnector; //# sourceMappingURL=LineConnector.js.map