btc-wallet: update build, fix routing

This commit is contained in:
Liam Fitzgerald 2021-09-13 11:19:11 +10:00
parent c992421366
commit 6b5942b59a
10 changed files with 357 additions and 28 deletions

View File

@ -1,6 +1,6 @@
const path = require('path');
const webpack = require('webpack');
// const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const urbitrc = require('./urbitrc');
const fs = require('fs-extra');
@ -33,6 +33,7 @@ let devServer = {
host: '0.0.0.0',
disableHostCheck: true,
historyApiFallback: true,
publicPath: '/apps/bitcoin/',
};
const router = _.mapKeys(urbitrc.FLEET || {}, (value, key) => `${key}.localhost:9000`);
@ -40,22 +41,19 @@ const router = _.mapKeys(urbitrc.FLEET || {}, (value, key) => `${key}.localhost:
if(urbitrc.URL) {
devServer = {
...devServer,
index: '',
proxy: {
'/~btc/js/bundle/index.*.js': {
target: 'http://localhost:9000',
pathRewrite: (req, path) => {
return '/index.js'
index: 'index.html',
proxy: [{
target: 'http://localhost:9000',
changeOrigin: true,
target: urbitrc.URL,
router,
context: path => {
if(path === '/apps/bitcoin/desk.js') {
return true;
}
},
'**': {
changeOrigin: true,
target: urbitrc.URL,
router,
// ensure proxy doesn't timeout channels
proxyTimeout: 0
return !path.startsWith('/apps/bitcoin')
}
}
}]
};
}
@ -107,7 +105,12 @@ module.exports = {
devtool: 'inline-source-map',
devServer: devServer,
plugins: [
new UrbitShipPlugin(urbitrc)
new UrbitShipPlugin(urbitrc),
new HtmlWebpackPlugin({
title: 'Bitcoin Wallet',
template: './public/index.html'
})
],
watch: true,
watchOptions: {
@ -118,7 +121,7 @@ module.exports = {
filename: 'index.js',
chunkFilename: 'index.js',
path: path.resolve(__dirname, '../dist'),
publicPath: '/',
publicPath: '/apps/bitcoin/',
globalObject: 'this'
},
optimization: {

View File

@ -1,5 +1,6 @@
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// const urbitrc = require('./urbitrc');
module.exports = {
@ -44,14 +45,18 @@ module.exports = {
},
devtool: 'source-map',
plugins: [
new CleanWebpackPlugin()
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Bitcoin Wallet',
template: './public/index.html'
})
],
output: {
filename: (pathData) => {
return pathData.chunk.name === 'app' ? 'index.[contenthash].js' : '[name].js';
},
path: path.resolve(__dirname, `../../arvo/app/btc-wallet/js/bundle`),
publicPath: '/',
path: path.resolve(__dirname, 'dist'),
publicPath: '/apps/bitcoin/',
},
optimization: {
minimize: true,

View File

@ -0,0 +1,30 @@
<!doctype html>
<html>
<head>
<title>Wallet</title>
<meta charset="utf-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no,maximum-scale=1"/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-touch-fullscreen" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<!--
<link rel="apple-touch-icon" href="/~btc/img/touch_icon.png">
<link rel="icon" type="image/png" href="/~btc/img/Favicon.png">
-->
<link rel="manifest"
href='data:application/manifest+json,{
"name": "Wallet",
"short_name": "Wallet",
"description": "A%20bitcoin%20wallet%20for%20urbit",
"display": "standalone",
"background_color": "%23FFFFFF",
"theme_color": "%23000000"}' />
</head>
<body>
<div id="root"></div>
<div id="portal-root"></div>
<script src="/apps/btc/desk.js"></script>
<script src="/session.js"></script>
</body>
</html>

View File

@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { Root } from './js/components/root.js';
import { api } from './js/api.js';
import { subscription } from "./js/subscription.js";
import Channel from './js/channel';
import './css/indigo-static.css';
import './css/fonts.css';
@ -10,7 +10,7 @@ import './css/custom.css';
// rebuild x3
const channel = new window.channel();
const channel = new Channel();
api.setChannel(window.ship, channel);

View File

@ -0,0 +1,290 @@
export default class Channel {
constructor() {
this.init();
this.deleteOnUnload();
// a way to handle channel errors
//
//
this.onChannelError = (err) => {
console.error('event source error: ', err);
};
this.onChannelOpen = (e) => {
console.log('open', e);
};
}
init() {
this.debounceInterval = 500;
// unique identifier: current time and random number
//
this.uid =
new Date().getTime().toString() +
"-" +
Math.random().toString(16).slice(-6);
this.requestId = 1;
// the currently connected EventSource
//
this.eventSource = null;
// the id of the last EventSource event we received
//
this.lastEventId = 0;
// this last event id acknowledgment sent to the server
//
this.lastAcknowledgedEventId = 0;
// a registry of requestId to successFunc/failureFunc
//
// These functions are registered during a +poke and are executed
// in the onServerEvent()/onServerError() callbacks. Only one of
// the functions will be called, and the outstanding poke will be
// removed after calling the success or failure function.
//
this.outstandingPokes = new Map();
// a registry of requestId to subscription functions.
//
// These functions are registered during a +subscribe and are
// executed in the onServerEvent()/onServerError() callbacks. The
// event function will be called whenever a new piece of data on this
// subscription is available, which may be 0, 1, or many times. The
// disconnect function may be called exactly once.
//
this.outstandingSubscriptions = new Map();
this.outstandingJSON = [];
this.debounceTimer = null;
}
resetDebounceTimer() {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
this.debounceTimer = null;
}
this.debounceTimer = setTimeout(() => {
this.sendJSONToChannel();
}, this.debounceInterval)
}
setOnChannelError(onError = (err) => {}) {
this.onChannelError = onError;
}
setOnChannelOpen(onOpen = (e) => {}) {
this.onChannelOpen = onOpen;
}
deleteOnUnload() {
window.addEventListener("beforeunload", (event) => {
this.delete();
});
}
clearQueue() {
clearTimeout(this.debounceTimer);
this.debounceTimer = null;
this.sendJSONToChannel();
}
// sends a poke to an app on an urbit ship
//
poke(ship, app, mark, json, successFunc, failureFunc) {
let id = this.nextId();
this.outstandingPokes.set(
id,
{
success: successFunc,
fail: failureFunc
}
);
const j = {
id,
action: "poke",
ship,
app,
mark,
json
};
this.sendJSONToChannel(j);
}
// subscribes to a path on an specific app and ship.
//
// Returns a subscription id, which is the same as the same internal id
// passed to your Urbit.
subscribe(
ship,
app,
path,
connectionErrFunc = () => {},
eventFunc = () => {},
quitFunc = () => {},
subAckFunc = () => {},
) {
let id = this.nextId();
this.outstandingSubscriptions.set(
id,
{
err: connectionErrFunc,
event: eventFunc,
quit: quitFunc,
subAck: subAckFunc
}
);
const json = {
id,
action: "subscribe",
ship,
app,
path
}
this.resetDebounceTimer();
this.outstandingJSON.push(json);
return id;
}
// quit the channel
//
delete() {
let id = this.nextId();
clearInterval(this.ackTimer);
navigator.sendBeacon(this.channelURL(), JSON.stringify([{
id,
action: "delete"
}]));
if (this.eventSource) {
this.eventSource.close();
}
}
// unsubscribe to a specific subscription
//
unsubscribe(subscription) {
let id = this.nextId();
this.sendJSONToChannel({
id,
action: "unsubscribe",
subscription
});
}
// sends a JSON command command to the server.
//
sendJSONToChannel(j) {
let req = new XMLHttpRequest();
req.open("PUT", this.channelURL());
req.setRequestHeader("Content-Type", "application/json");
if (this.lastEventId == this.lastAcknowledgedEventId) {
if (j) {
this.outstandingJSON.push(j);
}
if (this.outstandingJSON.length > 0) {
let x = JSON.stringify(this.outstandingJSON);
req.send(x);
}
} else {
// we add an acknowledgment to clear the server side queue
//
// The server side puts messages it sends us in a queue until we
// acknowledge that we received it.
//
let payload = [
...this.outstandingJSON,
{action: "ack", "event-id": this.lastEventId}
];
if (j) {
payload.push(j)
}
let x = JSON.stringify(payload);
req.send(x);
this.lastAcknowledgedEventId = this.lastEventId;
}
this.outstandingJSON = [];
this.connectIfDisconnected();
}
// connects to the EventSource if we are not currently connected
//
connectIfDisconnected() {
if (this.eventSource) {
return;
}
this.eventSource = new EventSource(this.channelURL(), {withCredentials:true});
this.eventSource.onmessage = e => {
this.lastEventId = parseInt(e.lastEventId, 10);
let obj = JSON.parse(e.data);
let pokeFuncs = this.outstandingPokes.get(obj.id);
let subFuncs = this.outstandingSubscriptions.get(obj.id);
if (obj.response == "poke" && !!pokeFuncs) {
let funcs = pokeFuncs;
if (obj.hasOwnProperty("ok")) {
funcs["success"]();
} else if (obj.hasOwnProperty("err")) {
funcs["fail"](obj.err);
} else {
console.error("Invalid poke response: ", obj);
}
this.outstandingPokes.delete(obj.id);
} else if (obj.response == "subscribe" ||
(obj.response == "poke" && !!subFuncs)) {
let funcs = subFuncs;
if (obj.hasOwnProperty("err")) {
funcs["err"](obj.err);
this.outstandingSubscriptions.delete(obj.id);
} else if (obj.hasOwnProperty("ok")) {
funcs["subAck"](obj);
}
} else if (obj.response == "diff") {
// ensure we ack before channel clogs
if((this.lastEventId - this.lastAcknowledgedEventId) > 30) {
this.clearQueue();
}
let funcs = subFuncs;
funcs["event"](obj.json);
} else if (obj.response == "quit") {
let funcs = subFuncs;
funcs["quit"](obj);
this.outstandingSubscriptions.delete(obj.id);
} else {
console.log("Unrecognized response: ", e);
}
}
this.eventSource.onopen = this.onChannelOpen;
this.eventSource.onerror = e => {
this.delete();
this.init();
this.onChannelError(e);
}
}
channelURL() {
return "/~/channel/" + this.uid;
}
nextId() {
return this.requestId++;
}
}

View File

@ -40,7 +40,7 @@ export default class Body extends Component {
} else {
return (
<Switch>
<Route path="/~btc/settings">
<Route path="/settings">
<Col
display='flex'
flexDirection='column'
@ -53,7 +53,7 @@ export default class Body extends Component {
/>
</Col>
</Route>
<Route path="/~btc">
<Route path="/">
<Col
display='flex'
flexDirection='column'

View File

@ -16,7 +16,7 @@ export default class Header extends Component {
render() {
let icon = this.props.settings ? "X" : "Adjust";
let iconColor = this.props.settings ? "black" : "orange";
let iconLink = this.props.settings ? "/~btc" : "/~btc/settings";
let iconLink = this.props.settings ? "/" : "/settings";
let connection = null;
let badge = null;

View File

@ -33,7 +33,7 @@ export class Root extends Component {
const blur = !loaded ? false : !(this.state.wallet && this.state.provider);
return (
<BrowserRouter>
<BrowserRouter basename="/apps/bitcoin">
<ThemeProvider theme={light}>
<Reset />
{loaded ? (

View File

@ -7,7 +7,7 @@ class Store {
constructor() {
this.state = {
loadedBtc: false,
loadedSettings: false,
loadedSettings: true,
loaded: false,
providerPerms: {},
shipWallets: {},
@ -23,7 +23,7 @@ class Store {
BTC: { last: 1, symbol: 'BTC' }
},
denomination: 'BTC',
showWarning: true,
showWarning: false,
error: '',
broadcastSuccess: false,
};

View File

@ -19,6 +19,7 @@ export class Subscription {
}
initializeSettings() {
return;
let app = 'settings-store';
let path = '/bucket/btc-wallet';