@mothepro/fancy-p2p
Version:
A quick and efficient way to form p2p groups in the browser
159 lines (158 loc) • 6.84 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
const lit_element_1 = require("lit-element");
require("lit-log");
const decoder = new TextDecoder, encoder = new TextEncoder, orderTestLimit = 5e3;
let default_1 = class default_1 extends lit_element_1.LitElement {
constructor() {
super(...arguments);
this.data = '';
this.replies = 0;
this.orderedMessages = [];
this.log = (...detail) => this.dispatchEvent(new CustomEvent('log', { detail, bubbles: true, composed: true }))
&& this.requestUpdate();
this.render = () => lit_element_1.html `
<lit-log open id="log" .entry=${this.chat}>
<span slot="summary">Chat</span>
Peers
<ul>
${[...this.peers].map(peer => lit_element_1.html `
<li =${this.sendDirect(peer)}>
${peer.name}
${peer.isYou ? '🌟' : ''}
</li>`)}
</ul>
<form =${this.sendData}>
<input
required
type="text"
name="data"
autocomplete="off"
placeholder="Message"
.value=${this.data}
=${({ target: { value } }) => this.data = value}
/>
<input type="submit" value="Broadcast">
</form>
<button =${this.sendRtt}>Latency Check</button>
<button =${this.sendRandom}>Generate Random Number</button>
<button
=${this.orderTest}
title=${`Sends ${orderTestLimit} packets and peers are expected to receive them all in order.`}
>Order check</button>
</lit-log>`;
this.sendData = (event) => {
event.preventDefault();
this.dispatchEvent(new CustomEvent('broadcast', { detail: encoder.encode(this.data), bubbles: true }));
this.data = '';
};
this.sendDirect = ({ name, send }) => (event) => {
try {
event.preventDefault();
send(encoder.encode(this.data));
this.log(`Sending ${name} "${this.data}"`);
this.data = '';
}
catch (err) {
this.log(err);
}
};
this.sendRandom = (event) => {
event.preventDefault();
this.dispatchEvent(new CustomEvent('broadcast', { detail: new Uint8Array([2 /* GENERATE_RANDOM */]), bubbles: true }));
};
this.sendRtt = (event) => {
event.preventDefault();
this.initRtt = this.elapsedTime;
this.dispatchEvent(new CustomEvent('broadcast', { detail: new Uint8Array([1 /* RTT */]), bubbles: true }));
};
this.orderTest = (event) => {
event.preventDefault();
const detail = new DataView(new ArrayBuffer(1 + 4));
detail.setInt8(0, 0 /* CHECK */);
for (let i = 0; i < orderTestLimit; i++) {
detail.setUint32(1, i, true);
this.dispatchEvent(new CustomEvent('broadcast', { detail, bubbles: true }));
}
};
}
/**
* Number of microseconds have passed since the page has opened.
* Could be innaccurate due to https://developer.mozilla.org/en-US/docs/Web/API/Performance/now#Reduced_time_precision
*/
get elapsedTime() {
return Math.trunc(1000 * performance.now());
}
firstUpdated() {
for (const peer of this.peers)
this.bindMessage(peer);
}
async bindMessage({ message, send, name }) {
try {
for await (const data of message) {
if (!(data instanceof ArrayBuffer))
throw Error(`${name} sent unexpected data: ${data}`);
const view = new DataView(data);
switch (view.getInt8(0)) {
case 0 /* CHECK */:
this.orderedMessages.push(view.getUint32(1, true));
if (this.orderedMessages.length == orderTestLimit) {
for (let i = 0; i < this.orderedMessages.length - 1; i++) {
if (this.orderedMessages[i] > this.orderedMessages[i + 1])
this.log(this.orderedMessages[i], 'should have come before', this.orderedMessages[i + 1]);
}
this.chat = `Finished checking order of ${orderTestLimit} messages`;
this.orderedMessages.length = 0;
}
break;
case 2 /* GENERATE_RANDOM */:
this.chat = `${name} shared the random integer ${this.nextRandom} for us`;
this.dispatchEvent(new CustomEvent('requestRNG', { bubbles: true }));
break;
case 1 /* RTT */:
if (this.initRtt) {
this.chat = `Round Trip Time with ${name} is ${this.elapsedTime - this.initRtt}μs`;
this.replies++;
}
else
send(new Uint8Array([1 /* RTT */]));
// All living peers responded
if (this.replies == this.peers.length) {
delete this.initRtt;
this.replies = 0;
}
break;
default:
this.chat = `${name} says "${decoder.decode(data)}"`;
}
}
}
catch (err) {
this.log(err);
}
this.log(`Connection with ${name} closed`);
}
};
__decorate([
lit_element_1.internalProperty()
], default_1.prototype, "data", void 0);
__decorate([
lit_element_1.internalProperty()
], default_1.prototype, "chat", void 0);
__decorate([
lit_element_1.property({ type: Number, attribute: 'next-random' })
], default_1.prototype, "nextRandom", void 0);
__decorate([
lit_element_1.property({ attribute: false })
], default_1.prototype, "peers", void 0);
default_1 = __decorate([
lit_element_1.customElement('lit-ready')
], default_1);
exports.default = default_1;
//# sourceMappingURL=ready.js.map