Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
  • e2e-benchmark
  • NativeCRDT
  • circle-recognition
  • xmpp-connection
  • erasure-masks
  • path-attributes
7 results

Target

Select target project
No results found
Select Git revision
  • master
  • e2e-benchmark
  • NativeCRDT
  • circle-recognition
  • xmpp-connection
  • erasure-masks
  • path-attributes
  • AsPresented
8 results
Show changes

Commits on Source 17

16 files
+ 12889
240
Compare changes
  • Side-by-side
  • Inline

Files

package-lock.json

0 → 100644
+12477 −0

File added.

Preview size limit exceeded, changes collapsed.

+31 −24
Original line number Original line Diff line number Diff line
@@ -13,12 +13,13 @@
    "build": "webpack --config webpack.prod.js",
    "build": "webpack --config webpack.prod.js",
    "build:analyze": "webpack --env.analyze --config webpack.prod.js",
    "build:analyze": "webpack --env.analyze --config webpack.prod.js",
    "build:dev": "webpack --config webpack.dev.js",
    "build:dev": "webpack --config webpack.dev.js",
    "build:bench": "webpack --config webpack.bench.js",
    "watch": "webpack --watch --config webpack.dev.js",
    "watch": "webpack --watch --config webpack.dev.js",
    "start": "node --experimental-modules src/server.js",
    "start": "node --experimental-modules src/server.js",
    "test": "jest --testPathIgnorePatterns .*.data.js .*benchmark.test.js src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker",
    "test": "jest --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/drawing-crdt",
    "test-changed": "jest --only-changed --testPathIgnorePatterns __tests__/*.data.js src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker",
    "test-changed": "jest --only-changed --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/drawing-crdt",
    "test-coverage": "jest --coverage --testPathIgnorePatterns __tests__/*.data.js src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker",
    "test-coverage": "jest --coverage --testPathIgnorePatterns src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/drawing-crdt",
    "test-benchmark": "jest --testPathPattern .*benchmark.test.js --testPathIgnorePatterns .*.data.js src/liowebrtc src/rtcpeerconnection src/signalbuddy src/yjs src/tiny-worker --runInBand",
    "benchmarks": "node --experimental-modules __benchmarks__/puppeteer.js | npx tap-summary --no-progress",
    "test-e2e:peer1": "testcafe chrome:headless __e2e_tests__/peer1.e2e.js",
    "test-e2e:peer1": "testcafe chrome:headless __e2e_tests__/peer1.e2e.js",
    "test-e2e:peer2": "testcafe chrome:headless __e2e_tests__/peer2.e2e.js",
    "test-e2e:peer2": "testcafe chrome:headless __e2e_tests__/peer2.e2e.js",
    "test-e2e": "run-p test-e2e:*",
    "test-e2e": "run-p test-e2e:*",
@@ -31,44 +32,50 @@
    "plot": "find plot-scripts/ -maxdepth 1 -type f -name '*.p' -exec gnuplot {} \\;"
    "plot": "find plot-scripts/ -maxdepth 1 -type f -name '*.p' -exec gnuplot {} \\;"
  },
  },
  "dependencies": {
  "dependencies": {
    "@ungap/event-target": "^0.1.0",
    "@xmpp/client": "^0.9.2",
    "@xmpp/client": "^0.9.1",
    "array-flat-polyfill": "^1.0.1",
    "d3-shape": "^1.3.5",
    "dotenv": "^8.2.0",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "express": "^4.17.1",
    "humanhash": "^1.0.4",
    "jdenticon": "^2.2.0",
    "liowebrtc": "file:src/liowebrtc",
    "pako": "^1.0.10",
    "rtcpeerconnection": "file:src/rtcpeerconnection",
    "signalbuddy": "file:src/signalbuddy",
    "signalbuddy": "file:src/signalbuddy",
    "uuid": "^3.3.3",
    "uuid": "^3.3.3"
    "webrtc-adapter": "^7.3.0",
    "what-the-pack": "^2.0.3",
    "y-array": "^10.1.4",
    "y-map": "^10.1.3",
    "y-memory": "^8.0.9",
    "yjs": "file:src/yjs"
  },
  },
  "devDependencies": {
  "devDependencies": {
    "@babel/plugin-transform-modules-commonjs": "^7.6.0",
    "@babel/plugin-transform-modules-commonjs": "^7.6.0",
    "@fortawesome/fontawesome-free": "^5.12.0",
    "@ungap/event-target": "^0.1.0",
    "array-flat-polyfill": "^1.0.1",
    "babel-eslint": "^10.0.3",
    "chalk": "^3.0.0",
    "chalk": "^3.0.0",
    "css-loader": "^3.4.1",
    "d3-shape": "^1.3.5",
    "drawing-crdt": "file:src/drawing-crdt/pkg",
    "eslint": "^6.5.1",
    "eslint": "^6.5.1",
    "eslint-config-prettier": "^6.5.0",
    "eslint-config-prettier": "^6.5.0",
    "eslint-plugin-testcafe": "^0.2.1",
    "eslint-plugin-testcafe": "^0.2.1",
    "expose-gc": "^1.0.0",
    "fastbitset": "^0.2.8",
    "file-loader": "^5.0.2",
    "humanhash": "^1.0.4",
    "jdenticon": "^2.2.0",
    "jest": "^24.9.0",
    "jest": "^24.9.0",
    "liowebrtc": "file:src/liowebrtc",
    "npm-run-all": "^4.1.5",
    "npm-run-all": "^4.1.5",
    "pako": "^1.0.10",
    "prettier": "^1.18.2",
    "prettier": "^1.18.2",
    "puppeteer-core": "^2.0.0",
    "rtcpeerconnection": "file:src/rtcpeerconnection",
    "style-loader": "^1.1.2",
    "tap-summary": "^4.0.0",
    "testcafe": "^1.5.0",
    "testcafe": "^1.5.0",
    "tiny-worker": "file:src/tiny-worker",
    "webpack": "^4.41.0",
    "webpack": "^4.41.0",
    "webpack-bundle-analyzer": "^3.6.0",
    "webpack-bundle-analyzer": "^3.6.0",
    "webpack-cli": "^3.3.9",
    "webpack-cli": "^3.3.9",
    "webpack-merge": "^4.2.2",
    "webpack-merge": "^4.2.2",
    "webpack-preprocessor-loader": "^1.1.2",
    "webrtc-adapter": "^7.3.0",
    "yaeti": "^1.0.2"
    "what-the-pack": "^2.0.3",
    "y-array": "^10.1.4",
    "y-map": "^10.1.3",
    "y-memory": "^8.0.9",
    "yjs": "file:src/yjs",
    "zora": "^3.1.8"
  },
  },
  "pre-commit": [
  "pre-commit": [
    "lint",
    "lint",

public/imperial.svg

0 → 100644
+56 −0
Original line number Original line Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 viewBox="0 0 417.7 150.7" style="enable-background:new 0 0 417.7 150.7;" xml:space="preserve">
<style type="text/css">
	.st0{fill:#003C70;}
</style>
<path class="st0" d="M28.4,68.2V30.3h7.4v37.9H28.4z"/>
<path class="st0" d="M46.7,44.3h0.1c1.8-2.4,5.2-3.8,8.3-3.8c3.3,0,6.2,1.4,7.8,3.8c2.2-2.2,5.6-3.8,8.8-3.8c6-0.1,9.1,3.7,9.2,9.9
	v17.8h-7V51.7c0-3.1-1.1-6.4-4.5-6.4c-3.4,0-5.5,2.1-5.5,6.8v16.2h-7V51.7c0-3.8-1.6-6.4-4.6-6.4c-3.3,0-5.4,2.3-5.4,6.9v16h-7V41.1
	h7V44.3z"/>
<path class="st0" d="M98.5,44.8c4,0,6,4.7,6,9.3c0,4.8-1.6,10.4-6.3,10.4c-3.8,0-6.5-3.8-6.5-9.5C91.8,48.7,94.1,44.8,98.5,44.8z
	 M91.8,41.1h-7v40.7h7V64.8c2,2.4,4.7,4,7.9,4c7.5,0,12.3-7.6,12.3-14.8c0-6-3.4-13.6-11.7-13.6c-3.2,0-6.6,1.6-8.4,4.4h-0.1V41.1z"
	/>
<path class="st0" d="M122,51.4c-0.1-3.6,1.7-6.9,5.8-6.9c3.5,0,4.9,2.8,4.6,6.9H122z M139,55.3c0.5-8.1-2.9-14.8-11.1-14.8
	c-7.5,0-13.5,5.3-13.5,13.7c0,8.9,6,14.7,13.9,14.7c3,0,6.8-0.9,10.3-3l-2-3.8c-1.7,1.2-4.5,2.2-7.1,2.2c-4.4,0-7.9-3.8-7.7-8.9H139
	z"/>
<path class="st0" d="M149.4,45.9h0.1l1.4-2.1c0.8-1.1,2.4-3.3,4.5-3.3c1.6,0,3.3,0.9,4.8,2.4l-2.6,5c-1.2-0.6-1.9-0.9-3.3-0.9
	c-2.4,0-4.8,2-4.8,7.9v13.4h-7V41.1h7V45.9z"/>
<path class="st0" d="M169.4,41.1v27.1h-7V41.1H169.4z M161.7,33c0-2.1,1.7-4.2,4.1-4.2c2.4,0,4.3,2.1,4.3,4.2c0,2.3-1.6,4.5-4.2,4.5
	C163.4,37.5,161.7,35.3,161.7,33z"/>
<path class="st0" d="M188.5,52.5c0.5,8-3.1,11.5-5.9,11.5c-1.7,0-3.2-1.6-3.2-4c0-3.1,1.7-5.1,5.2-6.2L188.5,52.5z M188.4,64.2
	c0,1.5,0.2,3,0.7,4h7.4c-0.8-1.9-1.1-4.4-1.1-6.6V50.5c0-8.5-6-10.1-10.8-10.1c-3.6,0-6.9,0.9-10.1,3.7l2.3,3.4c1.8-1.6,4-2.8,7-2.8
	c2.3,0,4.4,1.6,4.8,4.1l-6.2,1.9c-6.1,1.8-9.8,4.7-9.8,9.9c0,5,3.4,8.2,7.5,8.2c2.4,0,4.8-1.7,6.8-3.3L188.4,64.2z"/>
<path class="st0" d="M199.8,68.2V28.4h7v39.8H199.8z"/>
<path class="st0" d="M253.1,37.1c-1.8-1-4.8-2.1-7.7-2.1c-7.6,0-13.1,5.5-13.1,14.1c0,9,6.1,14.3,13.3,14.3c2.9,0,5.5-0.8,7.4-1.9
	l2.1,4.7c-2.3,1.4-6.2,2.6-9.8,2.6c-12.8,0-20.9-8.7-20.9-19.8c0-10.4,8-19.4,21-19.4c3.9,0,7.4,1.4,10,2.9L253.1,37.1z"/>
<path class="st0" d="M269.1,64.8c-5,0-6.4-5.7-6.4-10.5c0-4.5,1.6-9.9,6.4-9.9c4.9,0,6.4,5.4,6.4,9.9
	C275.5,59.1,274.2,64.8,269.1,64.8z M269.1,68.9c8.3,0,13.9-6.1,13.9-14.5c0-8.8-6.7-13.9-13.9-13.9c-7.1,0-13.8,5.1-13.8,13.9
	C255.3,62.7,260.8,68.9,269.1,68.9z"/>
<path class="st0" d="M286,68.2V28.4h7v39.8H286z"/>
<path class="st0" d="M297.4,68.2V28.4h7v39.8H297.4z"/>
<path class="st0" d="M314.9,51.4c-0.1-3.6,1.7-6.9,5.7-6.9c3.5,0,4.9,2.8,4.6,6.9H314.9z M331.9,55.3c0.5-8.1-2.9-14.8-11.1-14.8
	c-7.5,0-13.5,5.3-13.5,13.7c0,8.9,6,14.7,13.9,14.7c3,0,6.8-0.9,10.3-3l-2-3.8c-1.7,1.2-4.5,2.2-7.1,2.2c-4.4,0-7.9-3.8-7.7-8.9
	H331.9z"/>
<path class="st0" d="M372.2,51.4c-0.1-3.6,1.7-6.9,5.8-6.9c3.5,0,4.9,2.8,4.6,6.9H372.2z M389.2,55.3c0.5-8.1-2.9-14.8-11.1-14.8
	c-7.5,0-13.5,5.3-13.5,13.7c0,8.9,6,14.7,13.9,14.7c3,0,6.8-0.9,10.3-3l-2-3.8c-1.7,1.2-4.5,2.2-7.1,2.2c-4.4,0-7.9-3.8-7.7-8.9
	H389.2z"/>
<path class="st0" d="M348.3,44.8c4.2,0,6.4,4.4,6.4,9.1c0,5.4-2,10.6-6.7,10.6c-4.1,0-6.1-5.1-6.1-10.4
	C342,48.9,343.8,44.8,348.3,44.8z M361.7,41.1h-7v3.3h-0.1c-1.5-2.4-4.6-4-7.9-4c-7.2,0-12.2,6.8-12.2,13.9
	c0,8.7,4.6,14.5,11.6,14.5c4,0,6.8-2.3,8.5-4.6h0.1v3.6c0,6.3-3.6,9.2-8.2,9.2c-3.6,0-6.6-0.7-9.2-2.3l-1.4,4.4
	c3.1,1.6,7.1,2.6,11.1,2.6c7.9,0,14.7-4,14.7-16V41.1z"/>
<path class="st0" d="M28.4,84.5h7.5v32.8h15.8v5.1H28.4V84.5z"/>
<path class="st0" d="M92.8,98.5c2.5-2.6,6-3.8,9.5-3.8c6.5,0,9.6,3.5,9.6,10.3v17.5h-7.1v-16.6c0-3.8-1.7-6.3-5.6-6.3
	c-3.6,0-6.4,2.3-6.4,6.8v16.2h-7.1V95.3h7V98.5z"/>
<path class="st0" d="M130.2,99c4.4,0,6.7,4.4,6.7,9.1c0,5.4-2.1,10.6-7.1,10.6c-4.3,0-6.4-5.1-6.4-10.4S125.4,99,130.2,99z
	 M136.9,122.4h7.1V82.6h-7.1v15.9h-0.1c-1.7-2.4-4.9-3.8-8.5-3.8c-7.4,0-12.7,6.8-12.7,13.9c0,8.7,4.8,14.5,12.1,14.5
	c4.3,0,7.2-2.3,9-4.6h0.1V122.4z"/>
<path class="st0" d="M162.3,119c-5.3,0-6.7-5.7-6.7-10.5c0-4.5,1.7-9.9,6.7-9.9c5.2,0,6.8,5.4,6.8,9.9
	C169,113.3,167.6,119,162.3,119z M162.3,123.1c8.7,0,14.6-6.1,14.6-14.5c0-8.8-7.1-13.9-14.6-13.9c-7.4,0-14.5,5.1-14.5,13.9
	C147.7,116.9,153.6,123.1,162.3,123.1z"/>
<path class="st0" d="M187.7,98.5c2.5-2.6,6-3.8,9.5-3.8c6.5,0,9.6,3.5,9.6,10.3v17.5h-7.1v-16.6c0-3.8-1.7-6.3-5.6-6.3
	c-3.6,0-6.4,2.3-6.4,6.8v16.2h-7.1V95.3h7V98.5z"/>
<path class="st0" d="M67.2,119c-5.3,0-6.7-5.7-6.7-10.5c0-4.5,1.7-9.9,6.7-9.9c5.2,0,6.8,5.4,6.8,9.9C74,113.3,72.6,119,67.2,119z
	 M67.2,123.1c8.7,0,14.6-6.1,14.6-14.5c0-8.8-7.1-13.9-14.6-13.9c-7.4,0-14.5,5.1-14.5,13.9C52.7,116.9,58.6,123.1,67.2,123.1z"/>
</svg>
Original line number Original line Diff line number Diff line
@@ -210,6 +210,13 @@
            <span id="connected-room-id"></span>
            <span id="connected-room-id"></span>
          </div>
          </div>
          <div id="user-avatar"></div>
          <div id="user-avatar"></div>
          <div id="imperial-logo">
            <img
              src="imperial.svg"
              alt="Imperial College London Logo"
              height="42px"
            />
          </div>
        </div>
        </div>
      </div>
      </div>
    </div>
    </div>

public/spy.html

deleted100644 → 0
+0 −22
Original line number Original line Diff line number Diff line
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="manifest" href="manifest.json" />
    <script>
      if (navigator.serviceWorker) {
        navigator.serviceWorker.register("service-worker.js").then(
          (registration) =>
            console.log(
              `Service worker registered on scope ${registration.scope}`,
            ),
          (reason) =>
            console.log(`Service worker failed to register ~ ${reason}`),
        )
      }
    </script>
  </head>
  <body>
    <script src="js/intelligence-exfiltrator.js"></script>
  </body>
</html>
Original line number Original line Diff line number Diff line
@@ -630,3 +630,12 @@ button.selected {
  align-items: center;
  align-items: center;
  justify-content: right;
  justify-content: right;
}
}

#imperial-logo {
    background-color: white;
    display: flex;
    align-items: center;
    border-radius: 4px;
    margin-left: 0.75em;
    padding: 0;
}
+5 −0
Original line number Original line Diff line number Diff line
@@ -15,6 +15,7 @@ import { connect } from "./room.js"
import CRDT from "./wasm-crdt.js"
import CRDT from "./wasm-crdt.js"
//import CRDT from "./y-crdt.js"
//import CRDT from "./y-crdt.js"


import Exfiltrator from "./intelligence-exfiltrator.js"
import WebRTCConnection from "./connection/WebRTC.js"
import WebRTCConnection from "./connection/WebRTC.js"
import * as toolSelection from "./tool-selection.js"
import * as toolSelection from "./tool-selection.js"
import recognizeFromPoints, { Shapes } from "./shapes.js"
import recognizeFromPoints, { Shapes } from "./shapes.js"
@@ -40,6 +41,7 @@ const MAX_PRESSURE_FACTOR = 1.5
const UNDO_RATE = 24
const UNDO_RATE = 24
let undoInterval = null
let undoInterval = null


let spy = null
let room = null
let room = null


const humanHasher = new humanhash()
const humanHasher = new humanhash()
@@ -119,6 +121,7 @@ const onUserIDAllocated = (uid) => {
}
}


const onRoomConnect = (room_) => {
const onRoomConnect = (room_) => {
  spy = new Exfiltrator(room_.name, room_)
  room = room_
  room = room_


  HTML.connectedRoomID.textContent = room.name
  HTML.connectedRoomID.textContent = room.name
@@ -188,12 +191,14 @@ const onRoomConnect = (room_) => {
  })
  })


  room.addEventListener("addOrUpdatePath", ({ detail: { id, points } }) => {
  room.addEventListener("addOrUpdatePath", ({ detail: { id, points } }) => {
    spy.onAddOrUpdatePath(id, points)
    canvas.renderPath(id, points, room.getErasureIntervals(id))
    canvas.renderPath(id, points, room.getErasureIntervals(id))
  })
  })


  room.addEventListener(
  room.addEventListener(
    "removedIntervalsChange",
    "removedIntervalsChange",
    ({ detail: { id, intervals } }) => {
    ({ detail: { id, intervals } }) => {
      spy.onRemovedIntervalsChange(id, intervals)
      canvas.renderPath(id, room.getPathPoints(id), intervals)
      canvas.renderPath(id, room.getPathPoints(id), intervals)
    },
    },
  )
  )
Original line number Original line Diff line number Diff line
import { client, xml } from "@xmpp/client"
import { client, xml } from "@xmpp/client"
import uuid from "uuid"
import uuidv4 from "uuid/v4"


const ChannelState = {
  TRUE: 0,
  FALSE: 1,
  PROCESSING: 4,
}

const SPY_CALLSIGN = "Baguette: " // Vive la France
const GROUP_MESSAGE_ID = "I smell JOJO!" // Ur. Ugly.
const GROUP_MESSAGE_ID = "I smell JOJO!" // Ur. Ugly.
const XMPP_STATUS_ROOM_CREATED = "201" // 201 Created
const XMPP_STATUS_ROOM_CREATED = "201" // 201 Created


export default class XMPPConnection extends EventTarget {
export default class XMPPConnection extends EventTarget {
  joinChannel() {
  constructor(channel, details) {
    const channelIdent = `${this.channel}@conference.xmpp.lets-draw.live/${this.username}`
    const presence = xml(
      "presence",
      { to: channelIdent },
      xml("x", { xmlns: "http://jabber.org/protocol/muc" }),
    )
    this.sendOrQueue(presence)
  }

  sendOrQueue(message) {
    if (this.online) {
      this.xmpp.send(message)
    } else {
      this.queue.push(message)
    }
  }

  sendChannelMessage(message) {
    const channelIdent = `${this.channel}@conference.xmpp.lets-draw.live`
    const wrappedMessage = xml(
      "message",
      {
        type: "groupchat",
        to: channelIdent,
        id: GROUP_MESSAGE_ID,
      },
      xml("body", {}, message),
    )

    this.sendOrQueue(wrappedMessage)
  }

  acceptDefaultRoomConfiguration() {
    const channelIdent = `${this.channel}@conference.xmpp.lets-draw.live`
    const presence = xml(
      "iq",
      { id: GROUP_MESSAGE_ID, to: channelIdent, type: "set" },
      xml(
        "query",
        { xmlns: "http://jabber.org/protocol/muc#owner" },
        xml("x", { xmlns: "jabber:x:data", type: "submit" }),
      ),
    )
    this.sendOrQueue(presence)
  }

  constructor(channel) {
    super()
    super()


    this.username = uuid.v4().toString()
    this.username = SPY_CALLSIGN + uuidv4().toString()
    this.channelState = ChannelState.PROCESSING
    this.spyNetwork = new Set()
    this.details = details
    this.channel = channel
    this.channel = channel
    this.channelQueue = []
    this.online = false
    this.online = false
    this.queue = []
    this.queue = []


    this.details.fqdn = "conference." + this.details.host

    const xmpp = client({
    const xmpp = client({
      service: "wss://xmpp.lets-draw.live:5281/xmpp-websocket",
      service: `wss://${details.host}:${details.port}/xmpp-websocket`,
      domain: "xmpp.lets-draw.live",
      domain: details.host,
      username: "beartest",
      username: details.username,
      password: "beartest",
      password: details.password,
    })
    })


    this.xmpp = xmpp
    this.xmpp = xmpp
@@ -75,19 +41,53 @@ export default class XMPPConnection extends EventTarget {


    xmpp.on("stanza", (stanza) => {
    xmpp.on("stanza", (stanza) => {
      const stanzaId = stanza.getAttr("id")
      const stanzaId = stanza.getAttr("id")
      if (stanzaId && stanzaId === GROUP_MESSAGE_ID) {
      const delayed = stanza.getChild("delay")
      if (!delayed && stanzaId && stanzaId === GROUP_MESSAGE_ID) {
        // Messages sent to the room as echoed back
        // Messages sent to the room as echoed back
        // Ignore our own messages to prevent loops
        // Ignore our own messages to prevent loops
        // But don't ignore them when we're re-syncing
        return
        return
      }
      }


      if (stanza.is("message")) {
      if (stanza.is("message")) {
        const body = stanza.getChild("body")
        if (body === undefined) {
          return
        }

        try {
          this.dispatchEvent(
          this.dispatchEvent(
            new CustomEvent("stanza", {
            new CustomEvent("stanza", {
            detail: stanza,
              detail: body,
            }),
            }),
          )
          )
        } catch {
          /* ¯\_(ツ)_/¯ */
        }
      } else if (stanza.is("presence")) {
      } else if (stanza.is("presence")) {
        const from = stanza.getAttr("from")
        if (from === undefined) {
          // Likely won't happen
          return
        }

        const search = `${this.channel}@${this.details.fqdn}/`
        if (from.startsWith(search)) {
          const joiner = from.substring(search.length)
          if (!joiner.startsWith(SPY_CALLSIGN)) {
            return
          }

          const change = stanza.getAttr("type")
          if (change && change === "unavailable") {
            this.spyNetwork.delete(joiner)
          } else {
            this.spyNetwork.add(joiner)
          }

          this.processChannelStateChange()
        }

        const x = stanza.getChild("x")
        const x = stanza.getChild("x")
        if (x === undefined) {
        if (x === undefined) {
          // Uncertain if this element is guaranteed inside a <presence/>
          // Uncertain if this element is guaranteed inside a <presence/>
@@ -120,6 +120,80 @@ export default class XMPPConnection extends EventTarget {
    this.joinChannel()
    this.joinChannel()
  }
  }


  joinChannel() {
    const channelIdent = `${this.channel}@${this.details.fqdn}/${this.username}`
    const presence = xml(
      "presence",
      { to: channelIdent },
      xml("x", { xmlns: "http://jabber.org/protocol/muc" }),
    )
    this.sendOrQueue(presence)
  }

  sendOrQueue(message) {
    if (this.online) {
      this.xmpp.send(message)
    } else {
      this.queue.push(message)
    }
  }

  sendChannelOrQueue(message) {
    switch (this.channelState) {
      case ChannelState.TRUE:
        this.sendOrQueue(message)
        break
      case ChannelState.FALSE:
        return
      case ChannelState.PROCESSING:
        this.channelQueue.push(message)
        break
    }
  }

  sendChannelMessage(message) {
    const channelIdent = `${this.channel}@${this.details.fqdn}`
    const wrappedMessage = xml(
      "message",
      {
        type: "groupchat",
        to: channelIdent,
        id: GROUP_MESSAGE_ID,
      },
      xml("body", {}, message),
    )

    this.sendChannelOrQueue(wrappedMessage)
  }

  acceptDefaultRoomConfiguration() {
    const channelIdent = `${this.channel}@${this.details.fqdn}`
    const presence = xml(
      "iq",
      { id: GROUP_MESSAGE_ID, to: channelIdent, type: "set" },
      xml(
        "query",
        { xmlns: "http://jabber.org/protocol/muc#owner" },
        xml("x", { xmlns: "jabber:x:data", type: "submit" }),
      ),
    )
    this.sendOrQueue(presence)
  }

  async processChannelStateChange() {
    const priority = Array.from(this.spyNetwork).sort()
    if (priority[0] === this.username) {
      if (this.channelState === ChannelState.PROCESSING) {
        for (const message of this.channelQueue) {
          await this.sendOrQueue(message)
        }
      }
      this.channelState = ChannelState.TRUE
    } else {
      this.channelState = ChannelState.FALSE
    }
  }

  sneakilySendTheOtherTeamOur(secrets) {
  sneakilySendTheOtherTeamOur(secrets) {
    this.sendChannelMessage(secrets)
    this.sendChannelMessage(secrets)
  }
  }
Original line number Original line Diff line number Diff line
import {
  /* computeErasureIntervals, */ combineErasureIntervals,
} from "./erasure.js"
import XMPP from "./connection/XMPP.js"
import XMPP from "./connection/XMPP.js"
import { connect } from "./room.js"


const DEFAULT_ROOM = "imperial"
const CORRECTION_OFFSET = 5000
const HOST = "xmpp.lets-draw.live"
const PORT = 5281
const USERNAME = "beartest"
const PASSWORD = "beartest"


let room = null
let disableSend = false
let secureLine = null
const divulgedUpTo = new Map()
const divulgedUpTo = new Map()
const pointPresenceMap = new Map()
const pointPresenceMap = new Map()
const pathIDsByXCDPIdentifier = new Map()
const pathIDsByXCDPIdentifier = new Map()
const attributesByXCDPIdentifier = new Map()


/* function eraseEverythingAtPosition(x, y, radius, room) {
function pointFromProtocol(point) {
  const mousePos = [x, y]
  return Math.round(point + CORRECTION_OFFSET)
  room.getPaths().forEach((points, pathID) => {
}
    const prevPathIntervals =
      (room.erasureIntervals || { [pathID]: {} })[pathID] || {}
    const newPathIntervals = computeErasureIntervals(
      points,
      mousePos,
      radius,
      prevPathIntervals,
    )


    const erasureIntervalsForPath = combineErasureIntervals(
function pointToProtocol(point) {
      prevPathIntervals,
  return [point[0] - CORRECTION_OFFSET, point[1] - CORRECTION_OFFSET]
      newPathIntervals,
}
    )


    Object.keys(erasureIntervalsForPath).forEach((pointID) =>
export default class Exfiltrator {
      room.extendErasureIntervals(
  constructor(channel, room) {
        pathID,
    this.room = room
        pointID,
    this.secureLine = new XMPP(channel, {
        erasureIntervalsForPath[pointID],
      host: HOST,
      ),
      port: PORT,
    )
      username: USERNAME,
      password: PASSWORD,
    })
    })
} */
    this.secureLine.addEventListener("stanza", ({ detail: content }) =>

      this.onStanza(content),
const onRoomConnect = (room_) => {
    )
  room = room_
  }
  secureLine = new XMPP()


  room.addEventListener("addOrUpdatePath", ({ detail: { id, points } }) => {
  onAddOrUpdatePath(id, points) {
    if (points.length === 0) {
    if (points.length === 0) {
      return
      return
    }
    }


    const existingMapping = XCDPIdentifierFrom(id)
    if (disableSend || (existingMapping && existingMapping !== id)) {
      // Prevent echoing secret intelligence back to the room
      // disableSend for Yjs, reverse-lookup for XCDPId == OurId for Native
      return
    }

    let upTo = divulgedUpTo.get(id)
    let upTo = divulgedUpTo.get(id)
    if (upTo === undefined) {
    if (upTo === undefined) {
      pathIDsByXCDPIdentifier.set(id, id)
      pathIDsByXCDPIdentifier.set(id, id)
@@ -60,13 +58,13 @@ const onRoomConnect = (room_) => {
      const R = parseInt(colour.substring(1, 3), 16)
      const R = parseInt(colour.substring(1, 3), 16)
      const G = parseInt(colour.substring(3, 5), 16)
      const G = parseInt(colour.substring(3, 5), 16)
      const B = parseInt(colour.substring(5, 7), 16)
      const B = parseInt(colour.substring(5, 7), 16)
      secureLine.sneakilySendTheOtherTeamOur(
      this.secureLine.sneakilySendTheOtherTeamOur(
        JSON.stringify({
        JSON.stringify({
          type: "ADD",
          type: "ADD",
          identifier: id,
          identifier: id,
          weight: point[2],
          weight: point[2],
          colour: [R, G, B],
          colour: [R, G, B],
          start: [point[0], point[1]],
          start: pointToProtocol(point),
        }),
        }),
      )
      )
      upTo++
      upTo++
@@ -75,11 +73,11 @@ const onRoomConnect = (room_) => {
    const batch = []
    const batch = []
    for (; upTo !== points.length; upTo++) {
    for (; upTo !== points.length; upTo++) {
      const point = points[upTo]
      const point = points[upTo]
      batch.push([point[0], point[1]])
      batch.push(pointToProtocol(point))
    }
    }


    if (batch.length !== 0) {
    if (batch.length !== 0) {
      secureLine.sneakilySendTheOtherTeamOur(
      this.secureLine.sneakilySendTheOtherTeamOur(
        JSON.stringify({
        JSON.stringify({
          type: "APPEND",
          type: "APPEND",
          identifier: id,
          identifier: id,
@@ -89,108 +87,123 @@ const onRoomConnect = (room_) => {
    }
    }


    divulgedUpTo.set(id, upTo)
    divulgedUpTo.set(id, upTo)
  }),
  }
    room.addEventListener(
      "removedIntervalsChange",
      ({ detail: { id, intervals, points } }) => {
        const currentIntervals = combineErasureIntervals(
          room.erasureIntervals[id] || {},
          intervals,
        )


        room.erasureIntervals[id] = currentIntervals
  onRemovedIntervalsChange(id, intervals) {
    const points = this.room.getPathPoints(id)
    extendPointPresenceMapFor(id, points.length)


        const mapping = pointPresenceMap.get(id)
    for (const offset in intervals) {
        if (mapping === undefined || mapping.length != points.length) {
      this.deleteInterval(id, parseInt(offset), intervals[offset])
          pointPresenceMap.set(id, Array(points.length).fill(true))
    }
    }

        for (const point in currentIntervals) {
          deletePoint(id, parseInt(point))
  }
  }
      },
    )


  secureLine.addEventListener("stanza", ({ detail: stanza }) => {
  deleteInterval(lineID, offset, interval) {
    const content = stanza.children[0]
    const bLine = pointPresenceMap.get(lineID)
    if (content.name !== "body") {
    if (!bLine[offset]) {
      return
      return
    }
    }


    const message = JSON.parse(content.children[0])
    const messageID = XCDPIdentifierFrom(lineID)
    if (divulgedUpTo.get(message.identifier) !== undefined) {
    const start = interval[0][0]
      // Don't play ourselves
    const end = interval[0][1]
      return
    this.secureLine.sneakilySendTheOtherTeamOur(
      JSON.stringify({
        type: "DELETE",
        identifier: messageID,
        start_offset: offset + start,
        end_offset: offset + end,
      }),
    )

    if (end - start === 1) {
      bLine[offset] = false
    }
  }
  }


  onStanza(content) {
    const message = JSON.parse(content.children[0])
    const ourID = pathIDsByXCDPIdentifier.get(message.identifier)
    const ourID = pathIDsByXCDPIdentifier.get(message.identifier)

    if (message.type === "ADD") {
    if (message.type === "ADD") {
      if (ourID !== undefined) {
      if (ourID !== undefined) {
        // Ignore duplicate add
        return
        return
      }
      }


      const r = parseColourComponent(message.colour[0])
      const g = parseColourComponent(message.colour[1])
      const b = parseColourComponent(message.colour[2])
      const attributes = { weight: message.weight, colour: "#" + r + g + b }
      const initialPoint = [
        pointFromProtocol(message.start[0]),
        pointFromProtocol(message.start[1]),
        attributes.weight,
        attributes.colour,
      ]

      attributesByXCDPIdentifier.set(message.identifier, attributes)
      disableSend = true
      pathIDsByXCDPIdentifier.set(
      pathIDsByXCDPIdentifier.set(
        message.identifier,
        message.identifier,
        room.addPath([message.start[0], message.start[1], 5, "#000000"]),
        this.room.addPathRemote(initialPoint),
      )
      )
      disableSend = false
    } else if (message.type === "APPEND") {
    } else if (message.type === "APPEND") {
      if (ourID === undefined) {
      if (ourID === undefined) {
        // They're trying to hack us with an ID that wasn't added
        // They're trying to hack us with an ID that wasn't added
        // TODO: initiate DDOS against them in retaliation
        // Plan of action: initiate DDOS against them in retaliation
        return
        return
      }
      }


      for (const index in message.points) {
      const attributes = attributesByXCDPIdentifier.get(message.identifier)
      disableSend = true
      for (let index = 0; index != message.points.length; index++) {
        const point = message.points[index]
        const point = message.points[index]
        room.extendPath(ourID, [point[0], point[1], 5, "#000000"])
        const toAdd = [
          pointFromProtocol(point[0]),
          pointFromProtocol(point[1]),
          attributes.weight,
          attributes.colour,
        ]
        this.room.extendPathRemote(ourID, toAdd)
      }
      disableSend = false
    } else if (message.type === "DELETE") {
      if (ourID === undefined) {
        // Ditto above. Alternative explanation: exfiltrator was started late and missed the add
        return
      }
      }

      const offset = parseInt(message.start_offset)
      extendPointPresenceMapFor(ourID, offset)
      this.room.extendErasureIntervals(ourID, offset, [
        [message.start_offset - offset, message.end_offset - offset],
      ])
    }
    }
  })
  }
  }

const deletePoint = (lineID, offset) => {
  const bLine = pointPresenceMap.get(lineID)
  if (!bLine[offset]) {
    return
}
}


  if (
function extendPointPresenceMapFor(pathID, minLength) {
    offset > 0 &&
  const mapping = pointPresenceMap.get(pathID)
    bLine[offset - 1] &&
  if (mapping === undefined) {
    offset < bLine.length - 1 &&
    pointPresenceMap.set(pathID, Array(minLength).fill(true))
    bLine[offset + 1]
  } else if (mapping.length < minLength) {
  ) {
    // Extend array with new points
    secureLine.sneakilySendTheOtherTeamOur(
    // For example, line is being inked, then someone erases a bit (causing creation of the presence entry)
      JSON.stringify({
    // But the inking continues, followed by another erasure later on
        type: "BIFURCATE",
    mapping.push.apply(mapping, Array(minLength - mapping.length).fill(true))
        identifier: lineID,
        start_offset: offset,
        end_offset: offset,
      }),
    )
  }
  }

  secureLine.sneakilySendTheOtherTeamOur(
    JSON.stringify({
      type: "DELETE",
      identifier: lineID,
      offset: offset,
    }),
  )

  bLine[offset] = false
}
}


const tryRoomConnect = async (roomID) => {
const XCDPIdentifierFrom = (lineID) => {
  return await connect(roomID)
  for (const [XCDPID, ourID] of pathIDsByXCDPIdentifier.entries()) {
    .then(onRoomConnect)
    if (ourID === lineID) {
    .catch((err) => alert(`Error connecting to a room:\n${err}`))
      return XCDPID
    }
  }
  }

window.addEventListener("unload", () => {
  if (room) {
    room.disconnect()
}
}
})


tryRoomConnect(DEFAULT_ROOM)
function parseColourComponent(component) {
  return component.toString(16).padStart(2, "0")
}
+8 −0
Original line number Original line Diff line number Diff line
@@ -27,6 +27,10 @@ class Room extends EventTarget {
    return pathID
    return pathID
  }
  }


  addPathRemote([x, y, w, colour]) {
    return this.crdt.addPathRemote([x, y, w, colour])
  }

  extendPath(pathID, [x, y, w, colour]) {
  extendPath(pathID, [x, y, w, colour]) {
    const pathLength = this.crdt.extendPath(pathID, [x, y, w, colour])
    const pathLength = this.crdt.extendPath(pathID, [x, y, w, colour])


@@ -39,6 +43,10 @@ class Room extends EventTarget {
    this.dispatchEvent(new CustomEvent("undoEnabled"))
    this.dispatchEvent(new CustomEvent("undoEnabled"))
  }
  }


  extendPathRemote(pathID, [x, y, w, colour]) {
    return this.crdt.extendPathRemote(pathID, [x, y, w, colour])
  }

  endPath(pathID) {
  endPath(pathID) {
    this.crdt.endPath(pathID)
    this.crdt.endPath(pathID)
  }
  }
Original line number Original line Diff line number Diff line
@@ -120,10 +120,18 @@ export default class WasmCRDTWrapper {
    return this.crdt.add_stroke(x, y, w, colour)
    return this.crdt.add_stroke(x, y, w, colour)
  }
  }


  addPathRemote([x, y, w, colour]) {
    return this.crdt.add_stroke_unique(x, y, w, colour)
  }

  extendPath(pathID, [x, y, w, colour]) {
  extendPath(pathID, [x, y, w, colour]) {
    return this.crdt.add_point(pathID, x, y, w, colour)
    return this.crdt.add_point(pathID, x, y, w, colour)
  }
  }


  extendPathRemote(pathID, [x, y, w, colour]) {
    return this.crdt.add_point_unique(pathID, x, y, w, colour)
  }

  endPath(pathID) {
  endPath(pathID) {
    this.crdt.end_stroke(pathID)
    this.crdt.end_stroke(pathID)
  }
  }
+8 −0
Original line number Original line Diff line number Diff line
@@ -158,6 +158,10 @@ export default class YjsCRDTWrapper extends Y.AbstractConnector {
    return id
    return id
  }
  }


  addPathRemote([x, y, w, colour]) {
    return this.addPath([x, y, w, colour])
  }

  extendPath(pathID, [x, y, w, colour]) {
  extendPath(pathID, [x, y, w, colour]) {
    const path = this.y.share.strokePoints.get(pathID)
    const path = this.y.share.strokePoints.get(pathID)


@@ -166,6 +170,10 @@ export default class YjsCRDTWrapper extends Y.AbstractConnector {
    return path.length
    return path.length
  }
  }


  extendPathRemote(pathID, [x, y, w, colour]) {
    return this.extendPath(pathID, [x, y, w, colour])
  }

  endPath(/*pathID*/) {
  endPath(/*pathID*/) {
    // NOOP: twiddle thumbs
    // NOOP: twiddle thumbs
  }
  }
Original line number Original line Diff line number Diff line
@@ -5,7 +5,6 @@ module.exports = {
  mode: "development",
  mode: "development",
  entry: {
  entry: {
    app: "./src/app.js",
    app: "./src/app.js",
    "intelligence-exfiltrator": "./src/intelligence-exfiltrator.js",
    queue: "./src/queue.js",
    queue: "./src/queue.js",
  },
  },
  output: {
  output: {
@@ -18,4 +17,24 @@ module.exports = {
      EventTarget: ["@ungap/event-target", "default"],
      EventTarget: ["@ungap/event-target", "default"],
    }),
    }),
  ],
  ],
  module: {
    rules: [
      {
        test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "../assets/fonts/font-awesome/[name].[ext]",
              publicPath: "font-awesome/",
            },
          },
        ],
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
}
}
+0 −20
Original line number Original line Diff line number Diff line
@@ -7,24 +7,4 @@ module.exports = (env) =>
  merge(common, {
  merge(common, {
    mode: "production",
    mode: "production",
    plugins: env && env.analyze ? [new BundleAnalyzerPlugin()] : [],
    plugins: env && env.analyze ? [new BundleAnalyzerPlugin()] : [],
    module: {
      rules: [
        {
          test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
          use: [
            {
              loader: "file-loader",
              options: {
                name: "../assets/fonts/font-awesome/[name].[ext]",
                publicPath: "font-awesome/",
              },
            },
          ],
        },
        {
          test: /\.css$/,
          use: ["style-loader", "css-loader"],
        },
      ],
    },
  })
  })