-
Notifications
You must be signed in to change notification settings - Fork 194
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2270 from getAlby/feat/webln-getbalance
feat: webln.getBalance()
- Loading branch information
Showing
19 changed files
with
343 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
src/extension/background-script/actions/ln/__tests__/getBalance.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import utils from "~/common/lib/utils"; | ||
import { getBalanceOrPrompt } from "~/extension/background-script/actions/webln"; | ||
import type Connector from "~/extension/background-script/connectors/connector.interface"; | ||
import db from "~/extension/background-script/db"; | ||
import type { MessageBalanceGet, OriginData } from "~/types"; | ||
|
||
// suppress console logs when running tests | ||
console.error = jest.fn(); | ||
|
||
jest.mock("~/common/lib/utils", () => ({ | ||
openPrompt: jest.fn(() => Promise.resolve({ data: {} })), | ||
})); | ||
|
||
// overwrite "connector" in tests later | ||
let connector: Connector; | ||
const ConnectorClass = jest.fn().mockImplementation(() => { | ||
return connector; | ||
}); | ||
|
||
jest.mock("~/extension/background-script/state", () => ({ | ||
getState: () => ({ | ||
getConnector: jest.fn(() => Promise.resolve(new ConnectorClass())), | ||
currentAccountId: "8b7f1dc6-ab87-4c6c-bca5-19fa8632731e", | ||
}), | ||
})); | ||
|
||
const allowanceInDB = { | ||
enabled: true, | ||
host: "pro.kollider.xyz", | ||
id: 1, | ||
imageURL: "https://pro.kollider.xyz/favicon.ico", | ||
lastPaymentAt: 0, | ||
lnurlAuth: true, | ||
name: "pro kollider", | ||
remainingBudget: 500, | ||
totalBudget: 500, | ||
createdAt: "123456", | ||
tag: "", | ||
}; | ||
|
||
const permissionInDB = { | ||
id: 1, | ||
accountId: "8b7f1dc6-ab87-4c6c-bca5-19fa8632731e", | ||
allowanceId: allowanceInDB.id, | ||
createdAt: "1487076708000", | ||
host: allowanceInDB.host, | ||
method: "webln.getbalance", | ||
blocked: false, | ||
enabled: true, | ||
}; | ||
|
||
const message: MessageBalanceGet = { | ||
action: "getBalance", | ||
origin: { host: allowanceInDB.host } as OriginData, | ||
}; | ||
|
||
const requestResponse = { data: { balance: 123 } }; | ||
const fullConnector = { | ||
// hacky fix because Jest doesn't return constructor name | ||
constructor: { | ||
name: "lnd", | ||
}, | ||
getBalance: jest.fn(() => Promise.resolve(requestResponse)), | ||
} as unknown as Connector; | ||
|
||
// prepare DB with allowance | ||
db.allowances.bulkAdd([allowanceInDB]); | ||
|
||
// resets after every test | ||
afterEach(async () => { | ||
jest.clearAllMocks(); | ||
// ensure a clear permission table in DB | ||
await db.permissions.clear(); | ||
// set a default connector if overwritten in a previous test | ||
connector = fullConnector; | ||
}); | ||
|
||
describe("throws error", () => { | ||
test("if the host's allowance does not exist", async () => { | ||
const messageWithUndefinedAllowanceHost = { | ||
...message, | ||
origin: { | ||
...message.origin, | ||
host: "some-host", | ||
}, | ||
}; | ||
|
||
const result = await getBalanceOrPrompt(messageWithUndefinedAllowanceHost); | ||
|
||
expect(console.error).toHaveBeenCalledTimes(1); | ||
expect(result).toStrictEqual({ | ||
error: "Could not find an allowance for this host", | ||
}); | ||
}); | ||
|
||
test("if the getBalance call itself throws an exception", async () => { | ||
connector = { | ||
...fullConnector, | ||
getBalance: jest.fn(() => Promise.reject(new Error("Some API error"))), | ||
}; | ||
|
||
const result = await getBalanceOrPrompt(message); | ||
|
||
expect(console.error).toHaveBeenCalledTimes(1); | ||
expect(result).toStrictEqual({ | ||
error: "Some API error", | ||
}); | ||
}); | ||
}); | ||
|
||
describe("prompts the user first and then calls getBalance", () => { | ||
test("if the permission for getBalance does not exist", async () => { | ||
// prepare DB with other permission | ||
const otherPermission = { | ||
...permissionInDB, | ||
method: "webln/sendpayment", | ||
}; | ||
await db.permissions.bulkAdd([otherPermission]); | ||
|
||
const result = await getBalanceOrPrompt(message); | ||
|
||
expect(utils.openPrompt).toHaveBeenCalledWith({ | ||
args: { | ||
requestPermission: { | ||
method: "getBalance", | ||
description: "webln.getbalance", | ||
}, | ||
}, | ||
origin: message.origin, | ||
action: "public/confirmRequestPermission", | ||
}); | ||
expect(connector.getBalance).toHaveBeenCalled(); | ||
expect(result).toStrictEqual(requestResponse); | ||
}); | ||
|
||
test("if the permission for the getBalance exists but is not enabled", async () => { | ||
// prepare DB with disabled permission | ||
const disabledPermission = { | ||
...permissionInDB, | ||
enabled: false, | ||
}; | ||
await db.permissions.bulkAdd([disabledPermission]); | ||
|
||
const result = await getBalanceOrPrompt(message); | ||
|
||
expect(utils.openPrompt).toHaveBeenCalledWith({ | ||
args: { | ||
requestPermission: { | ||
method: "getBalance", | ||
description: "webln.getbalance", | ||
}, | ||
}, | ||
origin: message.origin, | ||
action: "public/confirmRequestPermission", | ||
}); | ||
expect(connector.getBalance).toHaveBeenCalled(); | ||
expect(result).toStrictEqual(requestResponse); | ||
}); | ||
}); | ||
|
||
describe("directly calls getBalance of Connector", () => { | ||
test("if permission for this website exists and is enabled", async () => { | ||
// prepare DB with matching permission | ||
await db.permissions.bulkAdd([permissionInDB]); | ||
|
||
// console.log(db.permissions); | ||
const result = await getBalanceOrPrompt(message); | ||
|
||
expect(connector.getBalance).toHaveBeenCalled(); | ||
expect(utils.openPrompt).not.toHaveBeenCalled(); | ||
expect(result).toStrictEqual(requestResponse); | ||
}); | ||
}); |
83 changes: 83 additions & 0 deletions
83
src/extension/background-script/actions/webln/getBalanceOrPrompt.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import utils from "~/common/lib/utils"; | ||
import db from "~/extension/background-script/db"; | ||
import state from "~/extension/background-script/state"; | ||
import { MessageDefault } from "~/types"; | ||
|
||
const getBalanceOrPrompt = async (message: MessageDefault) => { | ||
if (!("host" in message.origin)) return; | ||
|
||
const connector = await state.getState().getConnector(); | ||
const accountId = state.getState().currentAccountId; | ||
|
||
try { | ||
const allowance = await db.allowances | ||
.where("host") | ||
.equalsIgnoreCase(message.origin.host) | ||
.first(); | ||
|
||
if (!allowance?.id) { | ||
throw new Error("Could not find an allowance for this host"); | ||
} | ||
|
||
if (!accountId) { | ||
// type guard | ||
throw new Error("Could not find a selected account"); | ||
} | ||
|
||
const permission = await db.permissions | ||
.where("host") | ||
.equalsIgnoreCase(message.origin.host) | ||
.and((p) => p.accountId === accountId && p.method === "webln.getbalance") | ||
.first(); | ||
|
||
// request method is allowed to be called | ||
if (permission && permission.enabled) { | ||
const response = await connector.getBalance(); | ||
return response; | ||
} else { | ||
// throws an error if the user rejects | ||
const promptResponse = await utils.openPrompt<{ | ||
enabled: boolean; | ||
blocked: boolean; | ||
}>({ | ||
args: { | ||
requestPermission: { | ||
method: "getBalance", | ||
description: `webln.getbalance`, | ||
}, | ||
}, | ||
origin: message.origin, | ||
action: "public/confirmRequestPermission", | ||
}); | ||
|
||
const response = await connector.getBalance(); | ||
|
||
// add permission to db only if user decided to always allow this request | ||
if (promptResponse.data.enabled) { | ||
const permissionIsAdded = await db.permissions.add({ | ||
createdAt: Date.now().toString(), | ||
accountId: accountId, | ||
allowanceId: allowance.id, | ||
host: message.origin.host, | ||
method: "webln.getbalance", // ensure to store the prefixed method string | ||
enabled: promptResponse.data.enabled, | ||
blocked: promptResponse.data.blocked, | ||
}); | ||
|
||
!!permissionIsAdded && (await db.saveToStorage()); | ||
} | ||
|
||
return response; | ||
} | ||
} catch (e) { | ||
console.error(e); | ||
return { | ||
error: | ||
e instanceof Error | ||
? e.message | ||
: `Something went wrong with during webln.getBalance()`, | ||
}; | ||
} | ||
}; | ||
|
||
export default getBalanceOrPrompt; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.