6ee6fd64dd
Introduce page edit sessions with draft preview and patch API, chat skill picker, user memory profile, h5ApiBase resolution, voice WAV transport, and scripts for 105/g2 deployment and Plaza local dev. Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
3.2 KiB
JavaScript
116 lines
3.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Minimal DNS server for plaza.tkmind.cn -> 127.0.0.1
|
|
* Works with /etc/resolver/plaza.tkmind.cn (macOS per-domain DNS override).
|
|
*
|
|
* Usage:
|
|
* node scripts/plaza-local-dns-server.mjs
|
|
*/
|
|
import dgram from 'node:dgram';
|
|
|
|
const host = process.env.PLAZA_LOCAL_HOST ?? 'plaza.tkmind.cn';
|
|
const ip = (process.env.PLAZA_LOCAL_IP ?? '127.0.0.1').split('.').map(Number);
|
|
const port = Number(process.env.PLAZA_LOCAL_DNS_PORT ?? 5533);
|
|
const bind = process.env.PLAZA_LOCAL_DNS_BIND ?? '127.0.0.1';
|
|
|
|
function encodeName(name) {
|
|
const parts = name.split('.').filter(Boolean);
|
|
return Buffer.concat([
|
|
...parts.map((part) => Buffer.concat([Buffer.from([part.length]), Buffer.from(part, 'ascii')])),
|
|
Buffer.from([0]),
|
|
]);
|
|
}
|
|
|
|
const hostEncoded = encodeName(host);
|
|
|
|
function readQuestionName(msg, offset) {
|
|
const labels = [];
|
|
let pos = offset;
|
|
while (pos < msg.length) {
|
|
const len = msg[pos];
|
|
if (len === 0) {
|
|
pos += 1;
|
|
break;
|
|
}
|
|
labels.push(msg.subarray(pos + 1, pos + 1 + len).toString('ascii'));
|
|
pos += 1 + len;
|
|
}
|
|
return { name: labels.join('.'), next: pos };
|
|
}
|
|
|
|
function questionMatches(name) {
|
|
return name === host || name === `${host}.`;
|
|
}
|
|
|
|
const server = dgram.createSocket('udp4');
|
|
|
|
server.on('message', (msg, rinfo) => {
|
|
if (msg.length < 12) return;
|
|
|
|
const qdCount = msg.readUInt16BE(4);
|
|
if (qdCount !== 1) return;
|
|
|
|
const question = readQuestionName(msg, 12);
|
|
const qtype = msg.readUInt16BE(question.next);
|
|
const qclass = msg.readUInt16BE(question.next + 2);
|
|
const questionEnd = question.next + 4;
|
|
|
|
if (!questionMatches(question.name) || (qtype !== 1 && qtype !== 28) || qclass !== 1) {
|
|
return;
|
|
}
|
|
|
|
const questionSection = msg.subarray(12, questionEnd);
|
|
const ttl = Buffer.from([0x00, 0x00, 0x00, 0x3c]); // 60s
|
|
|
|
let answer;
|
|
if (qtype === 1) {
|
|
answer = Buffer.concat([
|
|
Buffer.from([0xc0, 0x0c]), // pointer to question name
|
|
Buffer.from([0x00, 0x01, 0x00, 0x01]), // A, IN
|
|
ttl,
|
|
Buffer.from([0x00, 0x04]),
|
|
Buffer.from(ip),
|
|
]);
|
|
} else {
|
|
// AAAA for ::ffff:127.0.0.1 so browsers don't prefer Tailscale fake-ip v6
|
|
answer = Buffer.concat([
|
|
Buffer.from([0xc0, 0x0c]),
|
|
Buffer.from([0x00, 0x1c, 0x00, 0x01]), // AAAA, IN
|
|
ttl,
|
|
Buffer.from([0x00, 0x10]),
|
|
Buffer.from([
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, ip[0], ip[1], ip[2], ip[3],
|
|
]),
|
|
]);
|
|
}
|
|
|
|
const header = Buffer.alloc(12);
|
|
header.writeUInt16BE(msg.readUInt16BE(0), 0); // ID
|
|
header.writeUInt16BE(0x8180, 2); // QR=1, AA=1, RD supported
|
|
header.writeUInt16BE(1, 4); // QDCOUNT
|
|
header.writeUInt16BE(1, 6); // ANCOUNT
|
|
header.writeUInt16BE(0, 8);
|
|
header.writeUInt16BE(0, 10);
|
|
|
|
const response = Buffer.concat([header, questionSection, answer]);
|
|
server.send(response, rinfo.port, rinfo.address);
|
|
});
|
|
|
|
server.on('error', (err) => {
|
|
if (err.code === 'EADDRINUSE') {
|
|
console.error(`Plaza DNS 端口 ${port} 已被占用`);
|
|
process.exit(1);
|
|
}
|
|
throw err;
|
|
});
|
|
|
|
server.bind(port, bind, () => {
|
|
console.log(`Plaza DNS ${host} -> ${ip.join('.')} @ ${bind}:${port}`);
|
|
});
|
|
|
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
process.on(signal, () => {
|
|
server.close(() => process.exit(0));
|
|
});
|
|
}
|