mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 04:13:17 +08:00
199 lines
5.9 KiB
JavaScript
199 lines
5.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { once } from 'node:events';
|
|
import { createServer, request } from 'node:http';
|
|
import { connect } from 'node:net';
|
|
import test from 'node:test';
|
|
import * as Lark from '@larksuiteoapi/node-sdk';
|
|
import { createFeishuWebSocketAgent } from '../../../plugin-src/host/channels/feishu/production.mjs';
|
|
import { verifyFeishuApp } from '../../../src/channels/feishu/feishu-app.mjs';
|
|
|
|
async function listen(server) {
|
|
server.listen(0, '127.0.0.1');
|
|
await once(server, 'listening');
|
|
return server.address().port;
|
|
}
|
|
|
|
function close(server) {
|
|
return new Promise((resolve, reject) => {
|
|
server.close((error) => (error ? reject(error) : resolve()));
|
|
});
|
|
}
|
|
|
|
function restoreEnvironment(snapshot) {
|
|
for (const [key, value] of snapshot) {
|
|
if (value === undefined) delete process.env[key];
|
|
else process.env[key] = value;
|
|
}
|
|
}
|
|
|
|
test('Feishu verification uses the SDK HTTP client through HTTPS_PROXY', {
|
|
timeout: 5000,
|
|
}, async () => {
|
|
const proxyHits = [];
|
|
const proxy = createServer((request, response) => {
|
|
proxyHits.push(request.url);
|
|
response.writeHead(502).end();
|
|
});
|
|
proxy.on('connect', (request, socket) => {
|
|
proxyHits.push(request.url);
|
|
socket.end('HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\n\r\n');
|
|
});
|
|
const proxyPort = await listen(proxy);
|
|
const environmentKeys = ['https_proxy', 'HTTPS_PROXY', 'no_proxy', 'NO_PROXY'];
|
|
const environment = new Map(environmentKeys.map((key) => [key, process.env[key]]));
|
|
|
|
try {
|
|
const proxyUrl = `http://127.0.0.1:${proxyPort}`;
|
|
process.env.https_proxy = proxyUrl;
|
|
process.env.HTTPS_PROXY = proxyUrl;
|
|
delete process.env.no_proxy;
|
|
delete process.env.NO_PROXY;
|
|
|
|
await assert.rejects(verifyFeishuApp({
|
|
appId: 'cli_0000000000000000',
|
|
appSecret: 'local-proxy-test-only',
|
|
httpInstance: Lark.defaultHttpInstance,
|
|
timeoutMs: 1000,
|
|
}));
|
|
} finally {
|
|
restoreEnvironment(environment);
|
|
await close(proxy);
|
|
}
|
|
|
|
assert.equal(proxyHits.length, 1);
|
|
assert.match(proxyHits[0], /open\.feishu\.cn/);
|
|
});
|
|
|
|
test('Lark WSClient sends its WSS handshake through the Feishu proxy agent', {
|
|
timeout: 5000,
|
|
}, async (t) => {
|
|
const connectTargets = [];
|
|
const proxy = createServer();
|
|
proxy.on('connect', (request, socket) => {
|
|
connectTargets.push(request.url);
|
|
socket.end('HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\n\r\n');
|
|
});
|
|
const proxyPort = await listen(proxy);
|
|
const agent = createFeishuWebSocketAgent({
|
|
HTTPS_PROXY: `http://127.0.0.1:${proxyPort}`,
|
|
});
|
|
const quietLogger = {
|
|
debug() {},
|
|
error() {},
|
|
info() {},
|
|
trace() {},
|
|
warn() {},
|
|
};
|
|
let failed;
|
|
const failure = new Promise((resolve, reject) => {
|
|
failed = { resolve, reject };
|
|
});
|
|
const wsClient = new Lark.WSClient({
|
|
agent,
|
|
appId: 'cli_0000000000000000',
|
|
appSecret: 'local-proxy-test-only',
|
|
autoReconnect: false,
|
|
handshakeTimeoutMs: 1000,
|
|
httpInstance: {
|
|
async request() {
|
|
return {
|
|
code: 0,
|
|
data: {
|
|
ClientConfig: {
|
|
PingInterval: 120,
|
|
ReconnectCount: 0,
|
|
ReconnectInterval: 1,
|
|
ReconnectNonce: 0,
|
|
},
|
|
URL: 'wss://msg-frontier.feishu.cn/ws/v2?device_id=local&service_id=1',
|
|
},
|
|
};
|
|
},
|
|
},
|
|
logger: quietLogger,
|
|
loggerLevel: Lark.LoggerLevel.error,
|
|
onError: () => failed.resolve(),
|
|
onReady: () => failed.reject(new Error('The synthetic 502 proxy unexpectedly connected')),
|
|
});
|
|
t.after(() => {
|
|
wsClient.close({ force: true });
|
|
agent.destroy();
|
|
});
|
|
t.after(() => close(proxy));
|
|
|
|
await wsClient.start({ eventDispatcher: {} });
|
|
await failure;
|
|
|
|
assert.deepEqual(connectTargets, ['msg-frontier.feishu.cn:443']);
|
|
});
|
|
|
|
test('Feishu WebSocket agent sends an upgrade through an HTTPS_PROXY CONNECT tunnel', {
|
|
timeout: 5000,
|
|
}, async (t) => {
|
|
let originSawUpgrade = false;
|
|
const origin = createServer();
|
|
origin.on('upgrade', (_request, socket) => {
|
|
originSawUpgrade = true;
|
|
socket.end([
|
|
'HTTP/1.1 101 Switching Protocols',
|
|
'Connection: Upgrade',
|
|
'Upgrade: websocket',
|
|
'',
|
|
'',
|
|
].join('\r\n'));
|
|
});
|
|
const originPort = await listen(origin);
|
|
|
|
const connectTargets = [];
|
|
const proxy = createServer();
|
|
proxy.on('connect', (proxyRequest, clientSocket, head) => {
|
|
connectTargets.push(proxyRequest.url);
|
|
const [hostname, port] = proxyRequest.url.split(':');
|
|
const upstream = connect({ host: hostname, port: Number(port) }, () => {
|
|
clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
|
if (head.length > 0) upstream.write(head);
|
|
upstream.pipe(clientSocket);
|
|
clientSocket.pipe(upstream);
|
|
});
|
|
upstream.on('error', (error) => clientSocket.destroy(error));
|
|
clientSocket.on('error', () => upstream.destroy());
|
|
});
|
|
const proxyPort = await listen(proxy);
|
|
|
|
const agent = createFeishuWebSocketAgent({
|
|
HTTPS_PROXY: `http://127.0.0.1:${proxyPort}`,
|
|
});
|
|
t.after(() => agent.destroy());
|
|
t.after(async () => {
|
|
await Promise.all([close(proxy), close(origin)]);
|
|
});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
const upgradeRequest = request({
|
|
agent,
|
|
headers: {
|
|
connection: 'Upgrade',
|
|
upgrade: 'websocket',
|
|
},
|
|
hostname: '127.0.0.1',
|
|
path: '/',
|
|
port: originPort,
|
|
});
|
|
upgradeRequest.once('upgrade', (_response, socket) => {
|
|
socket.destroy();
|
|
resolve();
|
|
});
|
|
upgradeRequest.once('response', (response) => {
|
|
reject(new Error(`Expected an upgrade, received HTTP ${response.statusCode}`));
|
|
});
|
|
upgradeRequest.once('error', reject);
|
|
upgradeRequest.end();
|
|
});
|
|
|
|
assert.equal(originSawUpgrade, true);
|
|
assert.deepEqual(connectTargets, [`127.0.0.1:${originPort}`]);
|
|
});
|
|
|
|
test('Feishu WebSocket agent is absent when no proxy is configured', () => {
|
|
assert.equal(createFeishuWebSocketAgent({}), undefined);
|
|
});
|