Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: commando connector #1577

Merged
merged 34 commits into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0ac3c0c
feat: start commando connector
kiwiidb Oct 6, 2022
3423c23
chore: storybook - apply webpack mjs config
escapedcat Oct 7, 2022
45d3f40
feat: implement get balance
Oct 11, 2022
a357be4
feat: add make invoice call
kiwiidb Oct 17, 2022
b570402
chore: bump lnmessage dep
kiwiidb Oct 18, 2022
be103cf
feat: add connectpeer call
kiwiidb Oct 18, 2022
95f6c41
feat: implement getinvoice response
kiwiidb Oct 18, 2022
1463471
feat: implement payinvoice response
kiwiidb Oct 18, 2022
7948844
feat: implement checkinvoice response
kiwiidb Oct 18, 2022
8649592
feat: implement missing methods
kiwiidb Oct 19, 2022
1b68578
chore: consistent params
kiwiidb Oct 19, 2022
f096ac3
fix: disconnect on unload
kiwiidb Oct 19, 2022
b955f77
feat: commando: add proxy and peer options
kiwiidb Oct 22, 2022
4342c28
feat: improve commando connector page
kiwiidb Oct 22, 2022
9b30c4e
fix: update lnmessage version
kiwiidb Oct 24, 2022
05c38d9
chore: fix merge conflicts
kiwiidb Oct 24, 2022
ff74995
feat: add private key
kiwiidb Oct 24, 2022
62f7ae0
fix: translation + keysend
kiwiidb Oct 24, 2022
588c248
chore: remove package lock file
kiwiidb Oct 24, 2022
34c29aa
fix: bumi PR remarks
kiwiidb Oct 26, 2022
3651846
Merge branch 'master' into feature/commando-connector
bumi Nov 1, 2022
d2e0e63
fix: use number field, no need to parse strings, my mistake
kiwiidb Nov 3, 2022
67353f7
chore: remove unnecessary code
kiwiidb Nov 4, 2022
45efa02
chore: 3 ='s
kiwiidb Nov 4, 2022
884c5f3
feat: add advanced settings
kiwiidb Nov 4, 2022
af591da
chore: update yarn lock
kiwiidb Nov 4, 2022
bb971f1
chore: use math.floor
kiwiidb Nov 4, 2022
6aaab06
fix: msatoshi_sent
kiwiidb Nov 4, 2022
d07982a
feat: translation advanced
kiwiidb Nov 4, 2022
d3e4a94
feat: disable getinvoices
kiwiidb Nov 4, 2022
7876df6
chore: add test
kiwiidb Nov 4, 2022
149c26e
feat: listinvoices
kiwiidb Nov 6, 2022
15d1a74
fix: milliseconds
kiwiidb Nov 6, 2022
96568dd
Merge branch 'master' into feature/commando-connector
bumi Nov 9, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ module.exports = {
],
include: path.resolve(__dirname, "../"),
});
config.module.rules.push({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is that for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To solve this:

ERROR in ./node_modules/lnmessage/dist/index.js 4:0-50
Module not found: Error: Can't resolve './crypto' in '/Users/kwinten/Alby/lightning-browser-extension/node_modules/lnmessage/dist'
Did you mean 'crypto.js'?
BREAKING CHANGE: The request './crypto' failed to resolve only because it was resolved as fully specified
(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.

I don't understand the details, maybe @escapedcat remembers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that lnmessage is being distributed as module:

{
  "name": "lnmessage",
  "version": "0.0.9",
  "description": "Talk to Lightning nodes from your browser",
  "main": "dist/index.js",
  "type": "module",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ],
  ...

But our build system can't handle this per default if it's not called a mjs file so we need to adjust the webpack config, which also effects our storybook config.

tbh I'm not sure if we still need storybook.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm... in alby-tools this worked fine: https://github.com/getAlby/alby-tools/blob/master/package.json#L5

also is storybook even using our build system? if the build system can not handle it then we would need some webpack config update I guess?

@kiwiidb where do you see that error that you posted? Can try to remove this and run it again?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I missed it's in the webpack config also.
but somewhat I would like to get rid of that and the npm package should be in a way that it also works for us - because I think we don't have such a strange build system.
maybe we can make a PR there?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Background: https://webpack.js.org/configuration/module/#resolvefullyspecified

Removing "type": "module", from the "lnmessage" package.json already makes it work without this special webpack option.
"type": "module", is letting node know that this is an esm package but this shouldn't be needed for a package that should run in the browser (only).

I can create a PR for this and ask for feedback.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's with the following package.json keys:
source, main, unpkg, exports ?

packages that can run in different environments should publish the package valid for the different environments.

Does the build step need to be improved? https://github.com/aaronbarnardsound/lnmessage/blob/master/package.json#L14
tsc is not enough?

I don't know enough about the build setups in 2022, but I used microbundle recently in this package: https://github.com/getAlby/alby-tools/blob/master/package.json#L28

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We started a discussion with Aaron on this and will solve this later on.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be removed no.
Actually if not 1000% needed I would like to resolve this now. because otherwise we will forget it and not take care of it and it will stay in there.

test: /\.m?js/,
resolve: {
fullySpecified: false,
},
});
config.resolve.plugins = [new TsconfigPathsPlugin()];
return config;
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"html5-qrcode": "^2.2.1",
"i18next": "^21.9.2",
"i18next-browser-languagedetector": "^6.1.5",
"lnmessage": "^0.0.3",
"lodash": "^4.17.21",
"pubsub-js": "^1.9.4",
"react": "^18.2.0",
Expand Down
14 changes: 14 additions & 0 deletions src/app/router/connectorRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import NewWallet from "@screens/connectors/NewWallet";
import i18n from "~/i18n/i18nConfig";
import { translationI18nNamespace } from "~/i18n/namespaces";

import ConnectCommando from "../screens/connectors/ConnectCommando";
import alby from "/static/assets/icons/alby.png";
import btcpay from "/static/assets/icons/btcpay.svg";
import citadel from "/static/assets/icons/citadel.png";
Expand Down Expand Up @@ -190,6 +191,19 @@ function getConnectorRoutes() {
),
logo: btcpay,
},
{
path: "commando",
element: <ConnectCommando />,
title: i18n.t(
"choose_connector.commando.title",
translationI18nNamespace
),
description: i18n.t(
"choose_connector.commando.description",
translationI18nNamespace
),
logo: btcpay,
},
];
}

Expand Down
131 changes: 131 additions & 0 deletions src/app/screens/connectors/ConnectCommando/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import ConnectorForm from "@components/ConnectorForm";
import TextField from "@components/form/TextField";
import ConnectionErrorToast from "@components/toasts/ConnectionErrorToast";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import utils from "~/common/lib/utils";

export default function ConnectCommando() {
const navigate = useNavigate();
const { t } = useTranslation("translation", {
keyPrefix: `choose_connector.commando`,
});
const [formData, setFormData] = useState({
host: "",
pubkey: "",
rune: "",
});
const [loading, setLoading] = useState(false);

function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
setFormData({
...formData,
[event.target.name]: event.target.value.trim(),
});
}

function getConnectorType() {
return "commando";
}

async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setLoading(true);
const host = formData.host;
const pubkey = formData.pubkey;
const rune = formData.rune;
const account = {
name: "commando",
config: {
host,
pubkey,
rune,
},
connector: getConnectorType(),
};

try {
let validation;
// TODO: for native connectors we currently skip the validation because it is too slow (booting up Tor etc.)
if (account.connector === "nativelndhub") {
kiwiidb marked this conversation as resolved.
Show resolved Hide resolved
validation = { valid: true, error: "" };
} else {
validation = await utils.call("validateAccount", account);
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please review this. this contains c&p code.
can we validate the connection for commando before we save it (can it be initialized easily and fast enough and then unloaded again?

if (validation.valid) {
const addResult = await utils.call("addAccount", account);
if (addResult.accountId) {
await utils.call("selectAccount", {
id: addResult.accountId,
});
navigate("/test-connection");
}
} else {
console.error(validation);
toast.error(
<ConnectionErrorToast message={validation.error as string} />
);
}
} catch (e) {
console.error(e);
let message = t("errors.connection_failed");
if (e instanceof Error) {
message += `\n\n${e.message}`;
}
toast.error(message);
}
setLoading(false);
}

return (
<ConnectorForm
title={t("page.title")}
description={t("page.description")}
submitLoading={loading}
submitDisabled={
formData.host === "" && formData.pubkey === "" && formData.rune === ""
}
onSubmit={handleSubmit}
>
<div className="mb-6">
<TextField
id="host"
label={t("host.label")}
type="text"
required
placeholder="host"
title="host"
value={formData.host}
onChange={handleChange}
/>
</div>
<div className="mb-6">
<TextField
id="pubkey"
label={t("pubkey.label")}
type="text"
required
placeholder="pubkey"
title="pubkey"
value={formData.pubkey}
onChange={handleChange}
/>
</div>
<div className="mb-6">
<TextField
id="rune"
label={t("rune.label")}
type="text"
required
placeholder="rune"
title="rune"
value={formData.rune}
onChange={handleChange}
/>
</div>
</ConnectorForm>
);
}
151 changes: 151 additions & 0 deletions src/extension/background-script/connectors/commando.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import LnMessage from "lnmessage";
import { v4 as uuidv4 } from "uuid";

import Connector, {
CheckPaymentArgs,
CheckPaymentResponse,
GetBalanceResponse,
GetInfoResponse,
GetInvoicesResponse,
KeysendArgs,
MakeInvoiceArgs,
MakeInvoiceResponse,
SendPaymentArgs,
SendPaymentResponse,
SignMessageArgs,
SignMessageResponse,
} from "./connector.interface";

interface Config {
host: string;
port: number;
rune: string;
pubkey: string;
wsProxy: string;
privateKey: string;
}

type CommandoGetInfoResponse = {
alias: string;
id: string;
color: string;
};
type CommandoMakeInvoiceResponse = {
bolt11: string;
payment_hash: string;
payment_secret: string;
};
type CommandoChannel = {
peer_id: string;
channel_sat: number;
amount_msat: number;
funding_txid: string;
funding_output: number;
connected: boolean;
state: string;
};
type CommandoListFundsResponse = {
channels: CommandoChannel[];
};

export default class Commando implements Connector {
config: Config;
ln: LnMessage;

constructor(config: Config) {
this.config = config;
this.ln = new LnMessage({
remoteNodePublicKey: this.config.pubkey,
wsProxy: this.config.wsProxy || "wss://lnwsproxy.regtest.getalby.com",
kiwiidb marked this conversation as resolved.
Show resolved Hide resolved
ip: this.config.host,
port: this.config.port || 9735,
privateKey:
this.config.privateKey ||
"d6a2eba36168cc31e97396a781a4dd46dd3648c001d3f4fde221d256e41715ea",
});
}

async init() {
// initiate the connection to the remote node
await this.ln.connect();
bumi marked this conversation as resolved.
Show resolved Hide resolved
}

unload() {
return Promise.resolve();
}

// not yet implemented
connectPeer() {
console.error(
`${this.constructor.name} does not implement the getInvoices call`
);
return new Error("Not yet supported with the currently used account.");
}

async getInvoices(): Promise<GetInvoicesResponse> {
throw new Error("Not yet supported with the currently used account.");
}

async getInfo(): Promise<GetInfoResponse> {
const response = (await this.ln.commando({
method: "getinfo",
params: [],
rune: this.config.rune,
})) as CommandoGetInfoResponse;
return {
data: {
alias: response.alias,
pubkey: response.id,
color: response.color,
},
};
}

async getBalance(): Promise<GetBalanceResponse> {
const response = (await this.ln.commando({
method: "listfunds",
params: [],
rune: this.config.rune,
})) as CommandoListFundsResponse;
let lnBalance = 0;
for (let i = 0; i < response.channels.length; i++) {
kiwiidb marked this conversation as resolved.
Show resolved Hide resolved
lnBalance = lnBalance + response.channels[i].channel_sat;
}
return {
data: {
balance: lnBalance,
},
};
}

async sendPayment(args: SendPaymentArgs): Promise<SendPaymentResponse> {
throw new Error("Not yet supported with the currently used account.");
}

async keysend(args: KeysendArgs): Promise<SendPaymentResponse> {
throw new Error("Not yet supported with the currently used account.");
}

async checkPayment(args: CheckPaymentArgs): Promise<CheckPaymentResponse> {
throw new Error("Not yet supported with the currently used account.");
}

signMessage(args: SignMessageArgs): Promise<SignMessageResponse> {
throw new Error("Not yet supported with the currently used account.");
}

async makeInvoice(args: MakeInvoiceArgs): Promise<MakeInvoiceResponse> {
const label = uuidv4();
const response = (await this.ln.commando({
method: "invoice",
params: [(args.amount as number) * 1000, label, args.memo],
rune: this.config.rune,
})) as CommandoMakeInvoiceResponse;
return {
data: {
paymentRequest: response.bolt11,
rHash: response.payment_hash,
},
};
}
}
2 changes: 2 additions & 0 deletions src/extension/background-script/connectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Citadel from "./citadel";
import Commando from "./commando";
import Eclair from "./eclair";
import Galoy from "./galoy";
import LnBits from "./lnbits";
Expand Down Expand Up @@ -28,6 +29,7 @@ const connectors = {
eclair: Eclair,
citadel: Citadel,
nativecitadel: NativeCitadel,
commando: Commando,
};

export default connectors;
6 changes: 6 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ var options = {
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: "asset/resource",
},
{
test: /\.m?js/,
resolve: {
fullySpecified: false
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@escapedcat what's the status here?
this still can not be removed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

},
],
},

Expand Down
Loading