Refactored webext background script

This commit is contained in:
Thomas Buckley-Houston 2018-01-10 22:54:51 +08:00
parent 27e0b2ddc6
commit d2aaa33963
22 changed files with 1382 additions and 219 deletions

View File

@ -1,21 +0,0 @@
{
"env" : {
"node" : true,
"browser" : true,
"webextensions": true,
"mocha": true
},
"globals": {
"DEVELOPMENT": true,
"PRODUCTION": true
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"extends": "eslint:recommended",
"rules": {
"no-console": 0,
"no-unused-vars": [2, {"args": "all", "argsIgnorePattern": "^_"}]
}
}

1
.eslintrc Symbolic link
View File

@ -0,0 +1 @@
webext/.eslintrc

2
.gitignore vendored
View File

@ -2,8 +2,10 @@ build/
*.log
*.out
blank.png
node_modules
interfacer/target
interfacer/vendor
webext/web-ext-artifacts
webext/node_modules
webext/dist

View File

@ -1,43 +0,0 @@
FROM alpine
COPY . /app
RUN echo "http://mirror1.hs-esslingen.de/pub/Mirrors/alpine/v3.3/main" > /etc/apk/repositories
RUN echo "http://mirror1.hs-esslingen.de/pub/Mirrors/alpine/v3.3/community" >> /etc/apk/repositories
RUN echo "@testing http://mirror1.hs-esslingen.de/pub/Mirrors/alpine/edge/testing" >> /etc/apk/repositories
# Main dependencies
RUN apk add --no-cache bc xvfb ttf-dejavu xdotool@testing ffmpeg openssh mosh firefox dbus
# Installing Hiptext, video to text renderer and our own interfacer.go
# Keep this all in one RUN command so that the resulting Docker image is smaller.
RUN apk --no-cache add --virtual build-dependencies \
build-base git go freetype-dev jpeg-dev ffmpeg-dev ragel libx11-dev libxt-dev libxext-dev \
&& apk --no-cache add libgflags-dev@testing glog-dev@testing \
&& mkdir -p build \
&& cd build \
# Currently need to use a patched vesion of hiptext that supports video streams and ffmpeg v3
# Watch: https://github.com/jart/hiptext/pull/27
&& git clone https://github.com/tombh/hiptext \
&& cd hiptext \
&& git checkout ffmpeg-updates-and-unicode-hack \
&& make \
# Alpine's version of `install` doesn't support the `--mode=` format
&& install -m 0755 hiptext /usr/local/bin \
&& cd ../.. && rm -rf build \
# Build the interfacer.go/xzoom code
&& export GOPATH=/go && export GOBIN=/app/interfacer \
&& cd /app/interfacer && go get && go build \
&& apk --no-cache del build-dependencies
# Generate host keys
RUN ssh-keygen -A
RUN sed -i 's/#Port 22/Port 7777/' /etc/ssh/sshd_config
RUN mkdir -p /app/logs
WORKDIR /app
CMD ["/usr/sbin/sshd", "-D"]

View File

@ -3,31 +3,30 @@ package main
import (
"flag"
"fmt"
"os"
"net/http"
"path/filepath"
"os"
"path/filepath"
"strings"
"github.com/gorilla/websocket"
// Termbox seems to be one of the best projects in any language for handling terminal input.
// It's cross-platform and the maintainer is disciplined about supporting the baseline of escape
// codes that work across the majority of terminals.
"github.com/nsf/termbox-go"
"github.com/nsf/termbox-go"
)
var (
logfile string
logfile string
websocketAddresss = flag.String("addr", ":3334", "Web socket service address")
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
stdinChannel = make(chan string)
)
func setupLogging() {
dir, err := os.Getwd()
if err != nil {
@ -57,7 +56,7 @@ func log(msg string) {
func initialise() {
setupTermbox()
setupLogging();
setupLogging()
}
func setupTermbox() {
@ -70,13 +69,11 @@ func setupTermbox() {
func sendTtySize() {
x, y := termbox.Size()
log(fmt.Sprintf("%d,%d", x, y))
stdinChannel <- fmt.Sprintf("/tty_size,%d,%d", x, y)
}
func readStdin() {
var event string;
var event string
defer termbox.Close()
for {
switch ev := termbox.PollEvent(); ev.Type {
@ -100,28 +97,54 @@ func socketReader(ws *websocket.Conn) {
defer ws.Close()
for {
_, message, err := ws.ReadMessage()
termbox.SetCursor(0, 0)
os.Stdout.Write([]byte(message))
termbox.Flush()
parts := strings.Split(string(message), ",")
command := parts[0]
if command == "/frame" {
termbox.SetCursor(0, 0)
os.Stdout.Write([]byte(strings.Join(parts[1:], ",")))
termbox.Flush()
} else {
log("WEBEXT: " + string(message))
}
if err != nil {
if websocket.IsCloseError(err, websocket.CloseGoingAway) {
log("Socket reader detected that the browser closed the websocket")
triggerSocketWriterClose()
return
}
panic(err)
}
}
}
// When the socket reader attempts to read from a closed websocket it quickly and
// simply closes its associated Go routine. However the socket writer won't
// automatically notice until it actually needs to send something. So we force that
// by sending this NOOP text.
// TODO: There's a potential race condition because new connections share the same
// Go channel. So we need to setup a new channel for every connection.
func triggerSocketWriterClose() {
stdinChannel <- "BROWSH CLIENT FORCING CLOSE OF WEBSOCKET WRITER"
}
func socketWriter(ws *websocket.Conn) {
var message string
defer ws.Close()
for {
message = <- stdinChannel
log(fmt.Sprintf("sending ... %s", message))
message = <-stdinChannel
log(fmt.Sprintf("TTY sending: %s", message))
if err := ws.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
if err == websocket.ErrCloseSent {
log("Socket writer detected that the browser closed the websocket")
return
}
panic(err)
}
}
}
func socketServer(w http.ResponseWriter, r *http.Request) {
log("Incoming web request from browser")
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
panic(err)
@ -135,9 +158,11 @@ func socketServer(w http.ResponseWriter, r *http.Request) {
func main() {
initialise()
log("Starting Browsh CLI client")
go readStdin()
http.HandleFunc("/", socketServer)
if err := http.ListenAndServe(*websocketAddresss, nil); err != nil {
panic(err)
}
log("Exiting at end of main()")
}

22
webext/.eslintrc Normal file
View File

@ -0,0 +1,22 @@
{
"env" : {
"es6": true,
"node" : true,
"browser" : true,
"webextensions": true,
"mocha": true
},
"globals": {
"DEVELOPMENT": true,
"PRODUCTION": true
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"extends": "eslint:recommended",
"rules": {
"no-unused-vars": [2, {"args": "all", "argsIgnorePattern": "^_"}]
}
}

View File

@ -1,84 +1,4 @@
let tty_width, tty_height;
import Boot from 'background/boot'
// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:3334');
new Boot();
// Connection opened
socket.addEventListener('open', function (_event) {
console.log('Connected to Browsh client');
});
// Listen for messages
socket.addEventListener('message', (event) => {
console.log('Message from server ', event.data);
const parts = event.data.split(',');
const command = parts[0];
if (command === '/tty_size') {
tty_width = parts[1];
tty_height = parts[2];
} else {
tabs[active_tab_id].postMessage(event.data);
}
});
let active_tab_id;
function handleActivated(active_info) {
console.log(active_info);
active_tab_id = active_info.id
}
browser.tabs.onActivated.addListener(handleActivated);
function handleRegistration(_request, sender, sendResponse) {
console.log('tab registered');
sendResponse(sender.tab);
if (sender.tab.active) active_tab_id = sender.tab.id;
}
browser.runtime.onMessage.addListener(handleRegistration);
let tabs = {};
function connected(channel) {
console.log('tab connected');
tabs[channel.name] = channel;
resizeBrowserWindow();
// Currently tabs will only ever send screen output over their channel
channel.onMessage.addListener(function(screen) {
socket.send(screen);
});
}
browser.runtime.onConnect.addListener(connected);
setInterval(() => {
if (tabs[active_tab_id] === undefined) return;
tabs[active_tab_id].postMessage(`/send_frame,${tty_width},${tty_height}`);
}, 1000);
function resizeBrowserWindow() {
// Does this include scrollbars???
const width = parseInt(Math.round(tty_width * 9)) + 4;
const height = parseInt(Math.round(tty_height * 20)) + 4; // this is actually line-height
var getting = browser.windows.getCurrent();
getting.then(
(windowInfo) => {
console.log('resizing window', windowInfo, width, height);
const updating = browser.windows.update(
windowInfo.id,
{
width: width,
height: height
}
);
updating(
(info) => {
console.log(info);
},
(error) => {
console.error(error);
}
);
},
(error) => {
console.error(error);
}
);
}

View File

@ -1,4 +1,4 @@
import FrameBuilder from 'frame_builder';
import FrameBuilder from 'dom/frame_builder';
new FrameBuilder();

991
webext/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,12 +3,17 @@
"test": "NODE_PATH=src:test mocha"
},
"babel": {
"presets": ["es2015"]
"presets": [
"es2015"
]
},
"devDependencies": {
"babel-eslint": "^8.1.2",
"babel-loader": "^7.1.2",
"babel-preset-es2015": "^6.24.1",
"babel-register": "^6.26.0",
"chai": "^4.1.2",
"eslint": "^4.15.0",
"mocha": "^4.1.0",
"sinon": "^4.1.3",
"webpack": "^3.10.0"

View File

@ -0,0 +1,108 @@
import mixins from 'mixin_factory';
import HubMixin from 'background/hub_mixin';
import TTYCommandsMixin from 'background/tty_commands_mixin';
import TabCommandsMixin from 'background/tab_commands_mixin';
// Boots the background process. Mainly involves connecting to the websocket server
// launched by the Browsh CLI client and setting up listeners for new tabs that
// have our webextension content script inside them.
export default class extends mixins(HubMixin, TTYCommandsMixin, TabCommandsMixin) {
constructor() {
super();
// Keep track of connections to active tabs
this.tabs = {};
// The ID of the tab currently opened in the browser
this.active_tab_id = null;
this._connectToTerminal();
}
_connectToTerminal() {
// This is the websocket server run by the CLI client
this.terminal = new WebSocket('ws://localhost:3334');
this.terminal.addEventListener('open', (_event) => {
this.log("Webextension connected to the terminal's websocket server");
this._connectToBrowser();
});
this._listenForTerminalMessages();
}
// Mostly listening for forwarded STDIN from the terminal. Therefore, the user
// pressing the arrow keys, typing, moving the mouse, etc, etc. But we also listen
// to TTY resize events too.
_listenForTerminalMessages() {
this.terminal.addEventListener('message', (event) => {
this.log('Message from terminal', event.data);
this.handleTerminalMessage(event.data)
});
}
_connectToBrowser() {
this._listenForNewTab();
this._listenForTabChannelOpen();
this._listenForFocussedTab();
this._startFrameRequestLoop();
}
// Curiously `browser.runtime.onMessage` receives the tab's ID, whereas
// `browser.runtime.onConnect` doesn't. So we have to have 2 tab listeners:
// 1. to get the tab ID so we can talk to it later.
// 2. to maintain a long-lived connection to continuously pass messages
// back and forth.
_listenForNewTab() {
browser.runtime.onMessage.addListener(this._newTabHandler.bind(this));
}
_newTabHandler(_request, sender, sendResponse) {
this.log(`Tab ${sender.tab.id} registered with background process`);
// Send the tab back to itself, such that it can be enlightened unto its own nature
sendResponse(sender.tab);
if (sender.tab.active) this.active_tab_id = sender.tab.id;
}
// This is the main communication channel for all back and forth messages to tabs
_listenForTabChannelOpen() {
browser.runtime.onConnect.addListener(this._tabChannelOpenHandler.bind(this));
}
_tabChannelOpenHandler(channel) {
this.log(`Tab ${channel.name} connected for communication with background process`);
this.tabs[channel.name] = channel;
channel.onMessage.addListener(this.handleTabMessage.bind(this));
}
_listenForFocussedTab() {
browser.tabs.onActivated.addListener(this._focussedTabHandler.bind(this));
}
_focussedTabHandler(tab) {
this.log(tab);
this.active_tab_id = tab.id
}
// The browser window can only be resized once we have both the character dimensions from
// the browser tab _and the TTY dimensions from the terminal. There's probably a more
// efficient way of triggering this initial window resize, than just waiting for the data
// on every frame tick.
_initialWindowResize() {
if (!this._is_intial_window_pending) return;
if(this.char_width && this.char_height && this.tty_width && this.tty_height) {
this.resizeBrowserWindow();
this._is_intial_window_pending = false;
}
}
// Instead of having each tab manage its own frame rate, just keep a single, centralised
// heartbeat in the background process that switches automatically to the current active
// tab.
_startFrameRequestLoop() {
setInterval(() => {
if (!this.tty_width || !this.tty_height) {
this.log("Not sending frame to TTY without TTY size")
return;
}
if (this._is_intial_window_pending) this._initialWindowResize();
if (!this.tabs.hasOwnProperty(this.active_tab_id)) return;
this.sendToCurrentTab('/request_frame');
}, 1000);
}
}

View File

@ -0,0 +1,16 @@
// Here we keep the public funcntions used to mediate communications between
// the background process, tabs and the terminal.
export default (Base) => class extends Base {
sendToCurrentTab(message) {
this.tabs[this.active_tab_id].postMessage(message);
}
sendToTerminal(message) {
this.terminal.send(message);
}
log(...message) {
if (message.length === 1) message = message[0];
this.sendToTerminal(message);
}
}

View File

@ -0,0 +1,31 @@
// Handle commands from tabs, like sending a frame or information about
// the current character dimensions .
export default (MixinBase) => class extends MixinBase {
handleTabMessage(message) {
const parts = message.split(',');
const command = parts[0];
switch (command) {
case '/frame':
// TODO: Add UI, tabs, etc
this.sendToTerminal(`/frame,${parts.slice(1).join(',')}`);
break;
case '/char_size':
this.char_width = parts[1];
this.char_height = parts[2]
if(this.tty_width && this.tty_height) this.resizeBrowserWindow();
break;
case '/request_tty_size':
this.sendTTYSizeToBrowser();
break;
case `/log`:
this.log(parts[1]);
break;
default:
this.log('Unknown command from tab to background', message);
}
}
sendTTYSizeToBrowser() {
this.sendToCurrentTab(`/tty_size,${this.tty_width},${this.tty_height}`);
}
};

View File

@ -0,0 +1,58 @@
// Handle commands coming in from the terminal, like; STDIN keystrokes, TTY resize, etc
export default (MixinBase) => class extends MixinBase {
handleTerminalMessage(message) {
const parts = message.split(',');
const command = parts[0];
if (command === '/tty_size') {
this.tty_width = parts[1];
this.tty_height = parts[2];
if (this.char_width && this.char_height) this.resizeBrowserWindow();
} else {
this.sendToCurrentTab(message);
}
}
resizeBrowserWindow() {
if (!this.tty_width || !this.char_width || !this.tty_height || !this.char_height) {
this.log(
'Not resizing browser window without all of the TTY and character dimensions'
);
return;
}
// Does this include scrollbars???
const window_width = parseInt(Math.round(this.tty_width * this.char_width)) + 4;
// This is actually line-height
const window_height = parseInt(Math.round(this.tty_height * this.char_height)) + 4;
const current_window = browser.windows.getCurrent();
current_window.then(
(active_window) => {
this._sendWindowResizeRequest(active_window, window_width, window_height);
},
(error) => {
this.log('Error getting current browser window', error);
}
);
}
_sendWindowResizeRequest(active_window, width, height) {
const tag = 'Resizing browser window';
this.log(tag, active_window, width, height);
const updating = browser.windows.update(
active_window.id,
{
width: width,
height: height,
focussed: false
}
);
updating(
(info) => {
this.log(tag, info);
},
(error) => {
this.log(tag, error);
}
);
}
}

View File

@ -1,10 +1,19 @@
export default class BaseBuilder {
_sendMessage(message) {
this.channel.postMessage(message);
}
_snap(number) {
return parseInt(Math.round(number));
}
_log(...messages) {
console.log(messages);
if (messages.length == 1 && messages instanceof String) {
messages = messages[0];
} else {
messages = JSON.stringify(messages);
}
this._sendMessage(`/log,${messages}`);
}
_logPerformance(work, reference) {

View File

@ -1,6 +1,6 @@
import BaseBuilder from 'base_builder';
import GraphicsBuilder from 'graphics_builder';
import TextBuilder from 'text_builder';
import BaseBuilder from 'dom/base_builder';
import GraphicsBuilder from 'dom/graphics_builder';
import TextBuilder from 'dom/text_builder';
// Takes the graphics and text from the current viewport, combines them, then
// sends it to the background process where the rest of the UI, like tabs,
@ -8,26 +8,22 @@ import TextBuilder from 'text_builder';
export default class FrameBuilder extends BaseBuilder{
constructor() {
super();
this.graphics_builder = new GraphicsBuilder();
this.text_builder = new TextBuilder(this);
// ID for element we place in the DOM to measure the size of a single monospace
// character.
this._measuring_box_id = 'browsh_em_measuring_box';
this.graphics_builder = new GraphicsBuilder();
this.text_builder = new TextBuilder(this, this.graphics_builder);
this._setupInit();
}
sendFrame(tty_width, tty_height) {
this._setupDimensions(tty_width, tty_height);
sendFrame() {
this._setupDimensions();
this._compileFrame();
this._buildFrame();
this._sendMessage(this.frame);
this._sendMessage(`/frame,${this.frame}`);
this._is_first_frame_finished = true;
}
_sendMessage(message) {
this.channel.postMessage(message);
}
_setupInit() {
document.addEventListener("DOMContentLoaded", () => {
this._init();
@ -42,8 +38,6 @@ export default class FrameBuilder extends BaseBuilder{
}
_init(delay = 0) {
this._log('Browsh init()');
this._calculateMonospaceDimensions();
// When the webext devtools auto reloads this code, the background process
// can sometimes still be loading, in which case we need to wait.
setTimeout(() => this._registerWithBackground(), delay);
@ -57,22 +51,39 @@ export default class FrameBuilder extends BaseBuilder{
);
}
// The background process tells us when it wants a frame.
_registrationSuccess(registered) {
this.channel = browser.runtime.connect({
// We need to give ourselves a unique channel name, so the background
// process can identify us amongst other tabs.
name: registered.id.toString()
});
this._postCommsInit();
}
_postCommsInit() {
this._log('Webextension postCommsInit()');
this._calculateMonospaceDimensions();
this.graphics_builder.channel = this.channel;
this.text_builder.channel = this.channel;
this._requestInitialTTYSize();
this._listenForBackgroundMessages();
}
_listenForBackgroundMessages() {
this.channel.onMessage.addListener((message) => {
const parts = message.split(',');
const command = parts[0];
const tty_width = parseInt(parts[1]);
const tty_height = parseInt(parts[2]);
if (command === '/send_frame') {
this.sendFrame(tty_width, tty_height);
} else {
console.log(message);
switch (command) {
case '/request_frame':
this.sendFrame();
break;
case '/tty_size':
this.tty_width = parseInt(parts[1]);
this.tty_height = parseInt(parts[2]);
this._log(`Tab received TTY size: ${this.tty_width}x${this.tty_height}`);
break;
default:
this._log('Unknown command sent to tab', message);
}
});
}
@ -81,6 +92,13 @@ export default class FrameBuilder extends BaseBuilder{
this._log(error);
}
// The background process can't send the TTY size as soon as it gets it because maybe
// the a tab doesn't exist yet. So we request it ourselves - because we'd have to be
// ready in order to request.
_requestInitialTTYSize() {
this._sendMessage('/request_tty_size');
}
// This is critical in order for the terminal to match the browser as closely as possible.
// Ideally we want the browser's window size to be exactly multiples of the terminal's
// dimensions. So if the terminal is 80x40 and the font-size is 12px (12x6 pixels), then
@ -98,10 +116,11 @@ export default class FrameBuilder extends BaseBuilder{
const element = this._getOrCreateMeasuringBox();
const dom_rect = element.getBoundingClientRect();
this.char_width = dom_rect.width;
this.char_height = 18.1;
this.char_height = dom_rect.height + 2;
this.text_builder.char_width = this.char_width;
this.text_builder.char_height = this.char_height;
console.log('char dimensions', this.char_width, this.char_height);
this._sendMessage(`/char_size,${this.char_width},${this.char_height}`);
this._log(`Tab char dimensions: ${this.char_width}x${this.char_height}`);
}
// Back when printing was done by physical stamps, it was convention to measure the
@ -123,13 +142,14 @@ export default class FrameBuilder extends BaseBuilder{
return document.getElementById(this._measuring_box_id);
}
_setupDimensions(tty_width, tty_height) {
this.tty_width = tty_width;
this.tty_height = tty_height;
this.frame_width = tty_width;
_setupDimensions() {
if (!this.tty_width || !this.tty_height) {
throw new Error("Frame Builder doesn't have the TTY dimensions");
}
this.frame_width = this.tty_width;
// A frame is 'taller' than the TTY because of the special UTF8 half-block
// trick.
this.frame_height = tty_height * 2;
this.frame_height = this.tty_height * 2;
}
_compileFrame() {

View File

@ -1,4 +1,4 @@
import BaseBuilder from 'base_builder';
import BaseBuilder from 'dom/base_builder';
// Converts an instance of the viewport into a an array of pixel values.
// Note, that it does this both with and without the text visible in order

View File

@ -1,13 +1,13 @@
import _ from 'lodash';
import BaseBuilder from 'base_builder';
import BaseBuilder from 'dom/base_builder';
// Convert the text on the page into a snapped 2-dimensional grid to be displayed directly
// in the terminal.
export default class TextBuillder extends BaseBuilder {
constructor(frame_builder, graphics_builder) {
constructor(frame_builder) {
super();
this.graphics_builder = graphics_builder;
this.graphics_builder = frame_builder.graphics_builder;
this.frame_builder = frame_builder;
this._updateState();
}

View File

@ -0,0 +1,5 @@
export default function (...mixins) {
return mixins.reduce((base, mixin) => {
return mixin(base);
}, class {});
}

View File

@ -1,9 +1,9 @@
import sandbox from 'helper';
import {expect} from 'chai';
import FrameBuilder from 'frame_builder';
import GraphicsBuilder from 'graphics_builder';
import TextBuilder from 'text_builder';
import FrameBuilder from 'dom/frame_builder';
import GraphicsBuilder from 'dom/graphics_builder';
import TextBuilder from 'dom/text_builder';
import canvas_pixels from 'fixtures/canvas_pixels';
import text_grid from 'fixtures/text_grid';
@ -17,7 +17,9 @@ describe('Frame Builder', ()=> {
});
it('should merge pixels and text into ANSI true colour syntax', ()=> {
frame_builder.sendFrame(3, 2);
frame_builder.tty_width = 3;
frame_builder.tty_height = 2;
frame_builder.sendFrame();
const frame = frame_builder.frame.replace(/\u001b\[/g, 'ESC');
expect(frame).to.eq(
'ESC38;2;0;0;0mESC48;2;111;111;111m▄' +

View File

@ -1,7 +1,7 @@
import sinon from 'sinon';
import GraphicsBuilder from 'graphics_builder';
import FrameBuilder from 'frame_builder';
import GraphicsBuilder from 'dom/graphics_builder';
import FrameBuilder from 'dom/frame_builder';
import MockRange from 'mocks/range'
var sandbox = sinon.sandbox.create();

View File

@ -1,8 +1,9 @@
import sandbox from 'helper';
import {expect} from 'chai';
import TextBuilder from 'text_builder';
import GraphicsBuilder from 'graphics_builder';
import FrameBuilder from 'dom/frame_builder';
import TextBuilder from 'dom/text_builder';
import GraphicsBuilder from 'dom/graphics_builder';
import text_nodes from 'fixtures/text_nodes';
import {with_text, without_text, scaled} from 'fixtures/canvas_pixels';
@ -14,19 +15,16 @@ let text_builder;
window.innerWidth = 3;
window.innerHeight = 4;
const frame = {
tty_width: 3,
tty_height: 2,
char_width: 1,
char_height: 2
}
function setup() {
let graphics_builder = new GraphicsBuilder();
graphics_builder.getSnapshotWithText();
graphics_builder.getSnapshotWithoutText();
graphics_builder.getScaledSnapshot(frame.width, frame.height);
text_builder = new TextBuilder(frame, graphics_builder);
let frame_builder = new FrameBuilder();
frame_builder.tty_width = 3
frame_builder.tty_height = 2
frame_builder.char_width = 1
frame_builder.char_height = 2
frame_builder.graphics_builder.getSnapshotWithText();
frame_builder.graphics_builder.getSnapshotWithoutText();
frame_builder.graphics_builder.getScaledSnapshot();
text_builder = new TextBuilder(frame_builder);
}
describe('Text Builder', ()=> {

View File

@ -22,5 +22,19 @@ module.exports = {
DEVELOPMENT: JSON.stringify(true),
PRODUCTION: JSON.stringify(false)
})
]
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [require('babel-plugin-transform-decorators-legacy').default]
}
}
}
]
}
};