bottender
Version:
A framework for building conversational user interfaces.
290 lines • 12.4 kB
JavaScript
;
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