n8n-nodes-feishu-lark
Version:
n8n custom nodes for n8n to interact with Feishu/Lark, including Lark Bot, Lark MCP, and Lark Trigger.
363 lines • 14.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WSClient = void 0;
const ws_1 = __importDefault(require("ws"));
const protoBuf = __importStar(require("./proto-buf"));
const ws_config_1 = require("./ws-config");
const data_cache_1 = require("./data-cache");
const enum_1 = require("./enum");
const pbbp2_1 = require("./proto-buf/pbbp2");
class WSClient {
constructor(params) {
this.wsConfig = new ws_config_1.WSConfig();
this.isConnecting = false;
this.shouldReconnect = true;
this.reconnectInfo = {
lastConnectTime: 0,
nextConnectTime: 0,
};
const { appId, appSecret, logger, helpers, agent, domain, autoReconnect = true } = params;
this.logger = logger;
this.agent = agent;
this.dataCache = new data_cache_1.DataCache({ logger: this.logger });
this.helpers = helpers;
this.wsConfig.updateClient({
appId,
appSecret,
domain,
});
this.wsConfig.updateWs({
autoReconnect,
});
}
async pullConnectConfig() {
const { appId, appSecret } = this.wsConfig.getClient();
try {
const response = await this.helpers.request({
method: 'POST',
url: this.wsConfig.wsConfigUrl,
body: {
AppID: appId,
AppSecret: appSecret,
},
headers: {
locale: 'zh',
},
timeout: 15000,
});
const { code, data: { URL: connectUrl, ClientConfig }, msg, } = JSON.parse(response);
if (code !== enum_1.ErrorCode.ok) {
this.logger.error(`[ws] code: ${code}, ${code === enum_1.ErrorCode.system_busy ? msg : 'system busy'}`);
if (code === enum_1.ErrorCode.system_busy || code === enum_1.ErrorCode.internal_error) {
return false;
}
}
const parsedUrl = new URL(connectUrl);
const device_id = parsedUrl.searchParams.get('device_id');
const service_id = parsedUrl.searchParams.get('service_id');
this.wsConfig.updateWs({
connectUrl,
deviceId: device_id,
serviceId: service_id,
pingInterval: ClientConfig.PingInterval * 1000,
reconnectCount: ClientConfig.ReconnectCount,
reconnectInterval: ClientConfig.ReconnectInterval * 1000,
reconnectNonce: ClientConfig.ReconnectNonce * 1000,
});
this.logger.debug(`[ws] get connect config success, ws url: ${connectUrl}`);
return true;
}
catch (e) {
this.logger.error('[ws]', (e === null || e === void 0 ? void 0 : e.message) || 'system busy');
return false;
}
}
connect() {
const connectUrl = this.wsConfig.getWS('connectUrl');
let wsInstance;
try {
const { agent } = this;
wsInstance = new ws_1.default(connectUrl, { agent });
}
catch (e) {
this.logger.error('[ws] new WebSocket error');
}
if (!wsInstance) {
return Promise.resolve(false);
}
return new Promise((resolve) => {
wsInstance.on('open', () => {
this.logger.debug('[ws] ws connect success');
this.wsConfig.setWSInstance(wsInstance);
this.pingLoop();
resolve(true);
});
wsInstance.on('error', () => {
this.logger.error('[ws] ws connect failed');
resolve(false);
});
});
}
async reConnect(isStart = false) {
if (this.isConnecting) {
this.logger.debug('[ws] repeat connection');
return;
}
this.isConnecting = true;
const tryConnect = () => {
this.reconnectInfo.lastConnectTime = Date.now();
return this.pullConnectConfig()
.then((isSuccess) => (isSuccess ? this.connect() : Promise.resolve(false)))
.then((isSuccess) => {
if (isSuccess) {
this.communicate();
return Promise.resolve(true);
}
return Promise.resolve(false);
});
};
if (this.pingInterval) {
clearTimeout(this.pingInterval);
}
const wsInstance = this.wsConfig.getWSInstance();
if (isStart) {
if (wsInstance) {
wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.terminate();
}
if (this.reconnectInterval) {
clearTimeout(this.reconnectInterval);
}
let isSuccess = false;
try {
isSuccess = await tryConnect();
}
finally {
this.isConnecting = false;
}
if (!isSuccess) {
this.logger.error('[ws] connect failed');
await this.reConnect();
}
this.logger.info('[ws] ws client ready');
return;
}
const { autoReconnect, reconnectNonce, reconnectCount, reconnectInterval } = this.wsConfig.getWS();
if (!autoReconnect) {
return;
}
this.logger.info('[ws] reconnect');
if (wsInstance) {
wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.terminate();
}
this.wsConfig.setWSInstance(null);
const reconnectNonceTime = reconnectNonce ? reconnectNonce * Math.random() : 0;
this.reconnectInterval = setTimeout(async () => {
(async function loopReConnect(count) {
count++;
const isSuccess = await tryConnect();
if (isSuccess) {
this.logger.debug('[ws] reconnect success');
this.isConnecting = false;
return;
}
this.logger.info(`[ws] unable to connect to the server after trying ${count} times`);
if (reconnectCount >= 0 && count >= reconnectCount) {
this.isConnecting = false;
return;
}
this.reconnectInterval = setTimeout(() => {
loopReConnect.bind(this)(count);
}, reconnectInterval);
this.reconnectInfo.nextConnectTime = Date.now() + reconnectInterval;
}).bind(this)(0);
}, reconnectNonceTime);
this.reconnectInfo.nextConnectTime = Date.now() + reconnectNonceTime;
}
pingLoop() {
const { serviceId, pingInterval } = this.wsConfig.getWS();
const wsInstance = this.wsConfig.getWSInstance();
if ((wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.readyState) === ws_1.default.OPEN) {
const frame = {
headers: [
{
key: enum_1.HeaderKey.type,
value: enum_1.MessageType.ping,
},
],
service: Number(serviceId),
method: enum_1.FrameType.control,
SeqID: 0,
LogID: 0,
};
this.sendMessage(frame);
this.logger.info('[ws] ping success');
}
this.pingInterval = setTimeout(this.pingLoop.bind(this), pingInterval);
}
communicate() {
const wsInstance = this.wsConfig.getWSInstance();
wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.on('message', async (buffer) => {
const data = protoBuf.decode(buffer);
const { method } = data;
if (method === enum_1.FrameType.control) {
await this.handleControlData(data);
}
if (method === enum_1.FrameType.data) {
await this.handleEventData(data);
}
});
wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.on('error', (e) => {
this.logger.error('[ws] ws error');
});
wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.on('close', () => {
if (this.shouldReconnect) {
this.logger.info('[ws] client closed unexpectedly, try to reconnect');
this.reConnect();
}
else {
this.logger.info('[ws] client closed');
}
});
}
async handleControlData(data) {
var _a;
const type = (_a = data.headers.find((item) => item.key === enum_1.HeaderKey.type)) === null || _a === void 0 ? void 0 : _a.value;
const payload = data.payload;
if (type === enum_1.MessageType.ping) {
return;
}
if (type === enum_1.MessageType.pong && payload) {
this.logger.info('[ws] receive pong');
const dataString = new TextDecoder('utf-8').decode(payload);
const { PingInterval, ReconnectCount, ReconnectInterval, ReconnectNonce } = JSON.parse(dataString);
this.wsConfig.updateWs({
pingInterval: PingInterval * 1000,
reconnectCount: ReconnectCount,
reconnectInterval: ReconnectInterval * 1000,
reconnectNonce: ReconnectNonce * 1000,
});
this.logger.info('[ws] update wsConfig with pong data');
}
}
async handleEventData(data) {
var _a;
const headers = data.headers.reduce((acc, cur) => {
acc[cur.key] = cur.value;
return acc;
}, {});
const { message_id, sum, seq, type, trace_id } = headers;
const payload = data.payload;
if (type !== enum_1.MessageType.event) {
return;
}
const mergedData = this.dataCache.mergeData({
message_id,
sum: Number(sum),
seq: Number(seq),
trace_id,
data: payload,
});
if (!mergedData) {
return;
}
this.logger.debug(`[ws] receive message, message_type: ${type}; message_id: ${message_id}; trace_id: ${trace_id}; data: ${mergedData.data}`);
const respPayload = {
code: enum_1.HttpStatusCode.ok,
};
const startTime = Date.now();
try {
const result = await ((_a = this.eventDispatcher) === null || _a === void 0 ? void 0 : _a.invoke(mergedData));
if (result) {
respPayload.data = Buffer.from(JSON.stringify(result)).toString('base64');
}
}
catch (error) {
respPayload.code = enum_1.HttpStatusCode.internal_server_error;
this.logger.error(`[ws] invoke event failed, message_type: ${type}; message_id: ${message_id}; trace_id: ${trace_id}; error: ${error}`);
}
const endTime = Date.now();
this.sendMessage({
...data,
headers: [...data.headers, { key: enum_1.HeaderKey.biz_rt, value: String(startTime - endTime) }],
payload: new TextEncoder().encode(JSON.stringify(respPayload)),
});
}
sendMessage(data) {
var _a;
const wsInstance = this.wsConfig.getWSInstance();
if ((wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.readyState) === ws_1.default.OPEN) {
const resp = pbbp2_1.pbbp2.Frame.encode(data).finish();
(_a = this.wsConfig.getWSInstance()) === null || _a === void 0 ? void 0 : _a.send(resp, (err) => {
if (err) {
this.logger.error('[ws] send data failed');
}
});
}
}
getReconnectInfo() {
return this.reconnectInfo;
}
async start(params) {
const { eventDispatcher } = params;
if (!eventDispatcher) {
this.logger.error('[ws] client need to start with a eventDispatcher');
return;
}
this.eventDispatcher = eventDispatcher;
this.reConnect(true);
}
async stop() {
this.shouldReconnect = false;
const wsInstance = this.wsConfig.getWSInstance();
if (wsInstance) {
wsInstance.terminate();
}
if (this.reconnectInterval) {
clearTimeout(this.reconnectInterval);
}
if (this.pingInterval) {
clearTimeout(this.pingInterval);
}
this.dataCache.clear();
this.eventDispatcher = undefined;
this.isConnecting = false;
}
}
exports.WSClient = WSClient;
//# sourceMappingURL=index.js.map