Files
2026-09-24 05:17:32 -04:00

110 lines
4.1 KiB
JavaScript

'use strict';
const crypto = require('crypto');
const fs = require('fs');
const http = require('http');
const net = require('net');
const os = require('os');
const path = require('path');
const { spawn } = require('child_process');
const SERVER_DIR = path.resolve(process.env.POKESERVER_DIR || path.join(__dirname, '..', '..', '..', 'pokeserver'));
const gifts = require(path.join(SERVER_DIR, 'gifts.js'));
function freePort() {
return new Promise((resolve, reject) => {
const s = net.createServer();
s.unref();
s.on('error', reject);
s.listen(0, '127.0.0.1', () => {
const port = s.address().port;
s.close(() => resolve(port));
});
});
}
function request(port, method, urlPath, body, password) {
return new Promise((resolve, reject) => {
const data = body === undefined ? null : Buffer.from(JSON.stringify(body), 'utf8');
const headers = {};
if (data) {
headers['Content-Type'] = 'application/json';
headers['Content-Length'] = data.length;
}
if (password) headers.Authorization = 'Basic ' + Buffer.from('admin:' + password).toString('base64');
const req = http.request({ host: '127.0.0.1', port, method, path: urlPath, headers }, (res) => {
const chunks = [];
res.on('data', (c) => chunks.push(c));
res.on('end', () => resolve({ code: res.statusCode, body: Buffer.concat(chunks).toString('utf8') }));
});
req.on('error', reject);
if (data) req.write(data);
req.end();
});
}
async function waitUp(port) {
for (let i = 0; i < 100; i++) {
try {
const r = await request(port, 'GET', gifts.FEED_PATH);
if (r.code === 200) return r;
} catch (e) {
await new Promise((r) => setTimeout(r, 100));
}
}
throw new Error('server did not come up');
}
async function main() {
const dir = fs.mkdtempSync(path.join(process.env.G3GIFT_TMP || os.tmpdir(), 'g3gift-'));
const keyPath = path.join(dir, 'gift-test.pem');
const { privateKey } = crypto.generateKeyPairSync('ed25519');
fs.writeFileSync(keyPath, privateKey.export({ format: 'pem', type: 'pkcs8' }), { mode: 0o600 });
const pub = gifts.publicKeyHex(privateKey);
const password = crypto.randomBytes(8).toString('hex');
const httpPort = await freePort();
const relayPort = await freePort();
const child = spawn(process.execPath, [path.join(SERVER_DIR, 'server.js')], {
cwd: SERVER_DIR,
env: Object.assign({}, process.env, {
PORT: String(relayPort),
HTTP_PORT: String(httpPort),
DASHBOARD_PASSWORD: password,
GIFT_KEY_PATH: keyPath,
DATA_DIR: path.join(dir, 'data'),
CART_MIRROR_ENABLED: '0',
STATS_ENABLED: '0',
}),
stdio: ['ignore', 'ignore', 'inherit'],
});
const stop = () => {
try { child.kill('SIGTERM'); } catch (e) {}
process.exit(0);
};
process.on('SIGTERM', stop);
process.on('SIGINT', stop);
child.on('exit', (code) => {
process.stderr.write('g3gift_server: pokeserver exited ' + code + '\n');
process.exit(1);
});
await waitUp(httpPort);
const body = [];
for (let i = 0; i < 10; i++) body.push(['Welcome to the POKéMON', 'WIRELESS CLUB news.', 'New WONDER CARDS arrive', 'over WIRELESS COMMUNICATION.'][i] || '');
const set = {
cards: ['mystic_ticket', 'mew', 'stamp_card'].map((key) => ({ key, enabled: true, card: gifts.presetByKey(key).card })),
news: [{ key: 'club_news', enabled: true, news: { key: 'club_news', id: 1, sendType: 0, bgType: 1, titleText: 'WIRELESS CLUB NEWS', bodyText: body } }],
};
const saved = await request(httpPort, 'POST', '/visible/gifts', set, password);
if (saved.code !== 200) throw new Error('publish failed ' + saved.code + ' ' + saved.body);
const feed = JSON.parse((await request(httpPort, 'GET', gifts.FEED_PATH)).body);
if (!gifts.verifyFeed(feed, pub)) throw new Error('feed does not verify under the test key');
fs.writeFileSync(path.join(dir, 'ready'), httpPort + ' ' + pub + '\n');
process.stdout.write('READY ' + httpPort + ' ' + pub + ' ' + dir + '\n');
}
main().catch((err) => {
process.stderr.write('g3gift_server: ' + (err && err.stack || err) + '\n');
process.exit(1);
});