Commit 0154b87c authored by Akari Labs's avatar Akari Labs

populate repo

parent e119cd76
export enum PixBlOpcode {
hello, ping, pixel, sync, select, subscribe
}
export enum PixBlEncoding {
json, msgpack
}
import msgpack from 'msgpack-lite';
import { PixBlOpcode } from '../enums';
function connect(url: string) {
const canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 600;
document.body.innerHTML = '';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
const socket = new WebSocket(url);
socket.binaryType = 'arraybuffer';
function send(opcode: PixBlOpcode, ...args: any[]) {
socket.send(msgpack.encode([opcode].concat(args)));
}
socket.onmessage = msg => {
const data = typeof msg.data === 'string' ? JSON.parse(msg.data) : msgpack.decode(new Uint8Array(msg.data));
switch (data[0]) {
case PixBlOpcode.hello:
//send(PixBlOpcode.select, PixBlEncoding.msgpack);
send(PixBlOpcode.subscribe);
break;
case PixBlOpcode.subscribe:
send(PixBlOpcode.sync);
break;
case PixBlOpcode.ping:
send(PixBlOpcode.ping);
break;
/*case PixBlOpcode.select:
send(PixBlOpcode.sync);
break;*/
case PixBlOpcode.pixel:
const pixels: number[][] = data[1];
for (var i = 0; i < pixels.length; i++) {
const pixel = pixels[i];
ctx.fillStyle = `#${pixel[2].toString(16).padStart(2, '0')}${pixel[3].toString(16).padStart(2, '0')}${pixel[4].toString(16).padStart(2, '0')}`;
ctx.fillRect(pixel[0], pixel[1], 1, 1);
}
case PixBlOpcode.sync:
var img = new Image;
img.onload = () => ctx.drawImage(img, 0, 0);
img.src = `data:image/png;base64,${Buffer.from(data[1]).toString('base64')}`;
break;
}
}
function setPixel(x: number, y: number, r: number, g: number, b: number) {
send(PixBlOpcode.pixel, x, y, r, g, b);
}
socket.onclose = msg => {
console.error('lost connection to server, reconnecting in 5 seconds');
setTimeout(() => connect(url), 5000);
}
window['pixelBlaster']['session'] = {
setPixel, ctx
};
}
window['pixelBlaster'] = {
connect
};
{
"name": "pixelblaster-frontend",
"version": "0.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"devDependencies": {
"ts-loader": "^6.1.2",
"typescript": "^3.6.3",
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9"
},
"dependencies": {
"@types/msgpack-lite": "^0.1.6",
"msgpack-lite": "^0.1.26"
}
}
{
"compilerOptions": {
"allowJs": true,
"module": "commonjs",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"moduleResolution": "node",
"outDir": "./dist/",
"typeRoots": [
"../node_modules/@types",
"src/@types"
],
"jsx": "react",
"jsxFactory": "inert"
}
}
\ No newline at end of file
const path = require('path');
const dev = process.env.NODE_ENV === 'dev';
const root = path.resolve(__dirname);
module.exports = {
entry: path.resolve(root, 'index.ts'),
mode: dev ? 'development' : 'production',
output: {
path: path.resolve(root, 'dist'),
filename: 'main.js'
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
}
}
\ No newline at end of file
#!/usr/bin/node
require('./lucidshield').boot('./index.ts', require('./tsconfig.json'));
import fs from 'fs';
import express from 'express';
import expressws from 'express-ws';
import WebSocket from 'ws';
import msgpack from 'msgpack-lite';
import { createCanvas, Image } from 'canvas';
import { PixBlOpcode, PixBlEncoding } from './enums';
interface PixBlClient {
id: number,
ws: WebSocket,
encoding: PixBlEncoding,
ping: any,
subscribed: boolean
}
console.debug('pixelblaster');
var stats = {
sent: 0,
broadcast: 0,
recv: 0,
vrecv: 0,
reqs: 0,
pixels: 0,
commits: 0,
lastCommit: new Date(0)
};
const script = fs.readFileSync('./frontend/dist/main.js').toString();
const app = expressws(express()).app;
const canvas = createCanvas(800, 600);
const ctx = canvas.getContext('2d');
if (fs.existsSync('stats.json')) {
console.debug('loading stats');
stats = JSON.parse(fs.readFileSync('stats.json').toString());
}
if (fs.existsSync('buffer.png')) {
console.debug('loading previous framebuffer');
const img = new Image;
img.src = fs.readFileSync('buffer.png');
ctx.drawImage(img, 0, 0);
}
else {
console.debug('no framebuffer found');
ctx.fillStyle = `#FFFFFF`;
ctx.fillRect(0, 0, 800, 600);
}
var bufferChangedSinceSave = false;
setInterval(() => {
stats.lastCommit = new Date();
console.debug('dumping stats to disk');
fs.writeFile('stats.json', JSON.stringify(stats), () => { });
if (!bufferChangedSinceSave) {
console.debug('framebuffer has not been altered');
return;
}
stats.commits++;
console.debug('dumping framebuffer to disk');
bufferChangedSinceSave = false;
fs.writeFile('buffer.png', canvas.toBuffer(), () => { });
}, 60 * 1000);
var accu = 0;
const clients: PixBlClient[] = [];
const encoders = [
function (data) {
return JSON.stringify(data);
},
function (data) {
return msgpack.encode(data);
}
];
function broadcast(data) {
stats.broadcast++;
clients.forEach(x => {
try {
if ((x.ws.readyState === WebSocket.OPEN) && x.subscribed) {
stats.sent++;
x.ws.send(encoders[x.encoding](data));
}
}
catch (e) { }
});
}
var queue: number[][] = [];
setInterval(() => {
if (queue.length > 0) {
broadcast([PixBlOpcode.pixel, queue.splice(0, 4096)]);
}
}, 65);
app.use((req, res, next) => {
stats.reqs++;
console.log(`${req.method} ${req.path}`);
next();
});
app.ws("/socket", (ws, req) => {
const state: PixBlClient = {
id: accu++,
ws,
encoding: PixBlEncoding.msgpack,
ping: setInterval(() => send([PixBlOpcode.ping]), 5000),
subscribed: false
};
function send(data) {
try {
if (state.ws.readyState === WebSocket.OPEN) {
stats.sent++;
state.ws.send(encoders[state.encoding](data));
}
}
catch (e) { }
}
clients.push(state);
console.log(`${state.id}: open from ${req.ip}`);
send([PixBlOpcode.hello, state.id]);
ws.on('close', () => {
clearInterval(state.ping);
clients.splice(clients.indexOf(state), 1);
console.log(`${state.id}: closed`);
});
ws.on('message', msg => {
stats.recv++;
try {
const data = msg instanceof Buffer ? msgpack.decode(msg) : ((typeof msg === 'string') ? JSON.parse(msg) : null);
if (data == null || !Array.isArray(data) || typeof data[0] !== 'number') {
console.warn(`received invalid data from ${state.id}`);
return;
}
switch (data[0]) {
case PixBlOpcode.ping:
stats.vrecv++;
break;
case PixBlOpcode.pixel:
bufferChangedSinceSave = true;
const args = {
x: data[1],
y: data[2],
r: data[3],
g: data[4],
b: data[5]
};
for (var k in args) if (typeof args[k] !== 'number') return;
if (args.x > 800 || args.x < 0 || args.y > 600 || args.y < 0) return;
ctx.fillStyle = `rgb(${args.r},${args.g},${args.b})`;
ctx.fillRect(args.x, args.y, 1, 1);
//broadcast([PixBlOpcode.pixel, args.x, args.y, args.r, args.g, args.b]);
queue.push([args.x, args.y, args.r, args.g, args.b]);
stats.pixels++;
stats.vrecv++;
break;
case PixBlOpcode.sync:
send([PixBlOpcode.sync, canvas.toBuffer()]);
stats.vrecv++;
break;
case PixBlOpcode.select:
if (typeof data[1] !== 'number' || data[1] < PixBlEncoding.json || data[1] > PixBlEncoding.msgpack) {
console.warn(`${state.id} attempted to select invalid encoding`);
return;
}
state.encoding = data[1];
send([PixBlOpcode.select, state.encoding]);
stats.vrecv++;
break;
case PixBlOpcode.subscribe:
state.subscribed = true;
send([PixBlOpcode.subscribe]);
console.debug(`${state.id} has subscribed to receive image updates`);
stats.vrecv++;
break;
default:
console.warn(`invalid opcode from ${state.id}`);
}
}
catch (e) {
console.error(`${state.id}: exception while processing message`);
console.error(e);
}
});
});
app.get('/', (req, res) => {
res.send(`<!DOCTYPE html>\n<html><head><title>pixelblaster</title></head><body><script>${script};pixelBlaster.connect(\`\${location.protocol==='https:'?'wss':'ws'}://\${location.host}/socket\`)</script></body></html>`)
res.end();
});
app.get('/xyzzy', (req, res) => {
res.type('text');
res.send('nothing happened');
res.end();
})
app.get('/stats', (req, res) => {
res.type('json');
res.send(JSON.stringify({
clients: {
online: clients.length,
total: accu
},
requests: stats.reqs,
messages: {
sent: stats.sent,
broadcasts: stats.broadcast,
receivedTotal: stats.recv,
receivedValid: stats.vrecv
},
storage: {
commits: stats.commits,
lastCommit: stats.lastCommit
},
pixels: stats.pixels
}, null, 2));
res.end();
});
app.get('/frame', (req, res) => {
res.type('png');
res.send(canvas.toBuffer());
res.end();
});
app.listen('8239');
const path = require('path');
const tsnode = require('ts-node');
module.exports = {
boot(entrypoint, config) {
if (config == null) config = require('./defaults.json');
tsnode.register(config);
return require(path.resolve(entrypoint));
}
}
\ No newline at end of file
{
"name": "pixelblaster",
"version": "0.0.0",
"description": "another r/place clone",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "bitflip",
"license": "MIT",
"dependencies": {
"@types/express": "^4.17.1",
"@types/express-ws": "^3.0.0",
"@types/msgpack-lite": "^0.1.6",
"canvas": "^2.6.0",
"express": "^4.17.1",
"express-ws": "^4.0.0",
"msgpack-lite": "^0.1.26",
"@types/node": "^12.7.5",
"ts-node": "^8.4.1",
"typescript": "^3.6.3"
}
}
{
"compilerOptions": {
"allowJs": true,
"module": "commonjs",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"moduleResolution": "node",
"typeRoots": [
"node_modules/@types",
"src/@types"
],
"baseUrl": "."
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment