diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-05-28 16:27:34 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-05-28 21:55:16 +0200 |
commit | e7fa87bcc0052e1e99c6894e7e27a122374956b3 (patch) | |
tree | 56c243d08ae357533ebdb4fbf41211aa0fc914ce /src | |
parent | 08bd3dc0e8a3c2370e4e8abbaa241eaafc144f4c (diff) |
documentation and tslint settings to check for docs
Diffstat (limited to 'src')
-rw-r--r-- | src/chromeBadge.ts | 24 | ||||
-rw-r--r-- | src/components.ts | 2 | ||||
-rw-r--r-- | src/crypto/cryptoWorker.ts | 25 | ||||
-rw-r--r-- | src/crypto/emscInterface.ts | 4 | ||||
-rw-r--r-- | src/crypto/nodeWorker.ts | 23 | ||||
-rw-r--r-- | src/helpers.ts | 8 | ||||
-rw-r--r-- | src/logging.ts | 71 | ||||
-rw-r--r-- | src/query.ts | 95 | ||||
-rw-r--r-- | src/types.ts | 523 | ||||
-rw-r--r-- | src/wallet-test.ts | 12 | ||||
-rw-r--r-- | src/wallet.ts | 183 | ||||
-rw-r--r-- | src/wxApi.ts | 70 | ||||
-rw-r--r-- | src/wxBackend.ts | 8 |
13 files changed, 915 insertions, 133 deletions
diff --git a/src/chromeBadge.ts b/src/chromeBadge.ts index 13716a64a..702cefea8 100644 --- a/src/chromeBadge.ts +++ b/src/chromeBadge.ts @@ -30,33 +30,37 @@ function rAF(cb: (ts: number) => void) { } +/** + * Badge for Chrome that renders a Taler logo with a rotating ring if some + * background activity is happening. + */ export class ChromeBadge implements Badge { - canvas: HTMLCanvasElement; - ctx: CanvasRenderingContext2D; + private canvas: HTMLCanvasElement; + private ctx: CanvasRenderingContext2D; /** * True if animation running. The animation * might still be running even if we're not busy anymore, * just to transition to the "normal" state in a animated way. */ - animationRunning: boolean = false; + private animationRunning: boolean = false; /** * Is the wallet still busy? Note that we do not stop the * animation immediately when the wallet goes idle, but * instead slowly close the gap. */ - isBusy: boolean = false; + private isBusy: boolean = false; /** * Current rotation angle, ranges from 0 to rotationAngleMax. */ - rotationAngle: number = 0; + private rotationAngle: number = 0; /** * While animating, how wide is the current gap in the circle? * Ranges from 0 to openMax. */ - gapWidth: number = 0; + private gapWidth: number = 0; /** * Maximum value for our rotationAngle, corresponds to 2 Pi. @@ -207,14 +211,6 @@ export class ChromeBadge implements Badge { rAF(step); } - setText(s: string) { - chrome.browserAction.setBadgeText({text: s}); - } - - setColor(c: string) { - chrome.browserAction.setBadgeBackgroundColor({color: c}); - } - startBusy() { if (this.isBusy) { return; diff --git a/src/components.ts b/src/components.ts index 9d1127e99..633438766 100644 --- a/src/components.ts +++ b/src/components.ts @@ -37,7 +37,7 @@ export interface StateHolder<T> { * but has multiple state holders. */ export abstract class ImplicitStateComponent<PropType> extends React.Component<PropType, any> { - _implicit = {needsUpdate: false, didMount: false}; + private _implicit = {needsUpdate: false, didMount: false}; componentDidMount() { this._implicit.didMount = true; if (this._implicit.needsUpdate) { diff --git a/src/crypto/cryptoWorker.ts b/src/crypto/cryptoWorker.ts index 9541b7442..1a337446d 100644 --- a/src/crypto/cryptoWorker.ts +++ b/src/crypto/cryptoWorker.ts @@ -108,6 +108,10 @@ namespace RpcFunctions { return preCoin; } + + /** + * Create and sign a message to request payback for a coin. + */ export function createPaybackRequest(coin: CoinRecord): PaybackRequest { const p = new native.PaybackRequestPS({ coin_blind: native.RsaBlindingKeySecret.fromCrock(coin.blindingKey), @@ -127,6 +131,9 @@ namespace RpcFunctions { } + /** + * Check if a payment signature is valid. + */ export function isValidPaymentSignature(sig: string, contractHash: string, merchantPub: string): boolean { const p = new native.PaymentSignaturePS({ contract_hash: native.HashCode.fromCrock(contractHash), @@ -140,6 +147,9 @@ namespace RpcFunctions { nativePub); } + /** + * Check if a wire fee is correctly signed. + */ export function isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean { const p = new native.MasterWireFeePS({ closing_fee: (new native.Amount(wf.closingFee)).toNbo(), @@ -160,6 +170,9 @@ namespace RpcFunctions { } + /** + * Check if the signature of a denomination is valid. + */ export function isValidDenom(denom: DenominationRecord, masterPub: string): boolean { const p = new native.DenominationKeyValidityPS({ @@ -189,6 +202,9 @@ namespace RpcFunctions { } + /** + * Create a new EdDSA key pair. + */ export function createEddsaKeypair(): {priv: string, pub: string} { const priv = native.EddsaPrivateKey.create(); const pub = priv.getPublicKey(); @@ -196,6 +212,9 @@ namespace RpcFunctions { } + /** + * Unblind a blindly signed value. + */ export function rsaUnblind(sig: string, bk: string, pk: string): string { const denomSig = native.rsaUnblind(native.RsaSignature.fromCrock(sig), native.RsaBlindingKeySecret.fromCrock(bk), @@ -278,6 +297,9 @@ namespace RpcFunctions { } + /** + * Create a new refresh session. + */ export function createRefreshSession(exchangeBaseUrl: string, kappa: number, meltCoin: CoinRecord, @@ -398,6 +420,9 @@ namespace RpcFunctions { return b.hash().toCrock(); } + /** + * Hash a denomination public key. + */ export function hashDenomPub(denomPub: string): string { return native.RsaPublicKey.fromCrock(denomPub).encode().hash().toCrock(); } diff --git a/src/crypto/emscInterface.ts b/src/crypto/emscInterface.ts index e00e67a84..f3aeb8272 100644 --- a/src/crypto/emscInterface.ts +++ b/src/crypto/emscInterface.ts @@ -259,7 +259,7 @@ interface Arena { * Arena that must be manually destroyed. */ class SimpleArena implements Arena { - heap: ArenaObject[]; + protected heap: ArenaObject[]; constructor() { this.heap = []; @@ -774,7 +774,7 @@ export class EccSignaturePurpose extends PackedArenaObject { return this.payloadSize + 8; } - payloadSize: number; + private payloadSize: number; constructor(purpose: SignaturePurpose, payload: PackedArenaObject, diff --git a/src/crypto/nodeWorker.ts b/src/crypto/nodeWorker.ts index 4352b66c2..fa942387a 100644 --- a/src/crypto/nodeWorker.ts +++ b/src/crypto/nodeWorker.ts @@ -22,10 +22,22 @@ const fork = require("child_process").fork; const nodeWorkerEntry = path.join(__dirname, "nodeWorkerEntry.js"); +/** + * Worker implementation that uses node subprocesses. + */ export class Worker { - child: any; + private child: any; + + /** + * Function to be called when we receive a message from the worker thread. + */ onmessage: undefined | ((m: any) => void); + + /** + * Function to be called when we receive an error from the worker thread. + */ onerror: undefined | ((m: any) => void); + constructor(scriptFilename: string) { this.child = fork(nodeWorkerEntry); this.onerror = undefined; @@ -55,6 +67,9 @@ export class Worker { this.child.send({scriptFilename, cwd: process.cwd()}); } + /** + * Add an event listener for either an "error" or "message" event. + */ addEventListener(event: "message" | "error", fn: (x: any) => void): void { switch (event) { case "message": @@ -66,10 +81,16 @@ export class Worker { } } + /** + * Send a message to the worker thread. + */ postMessage (msg: any) { this.child.send(JSON.stringify({data: msg})); } + /** + * Forcibly terminate the worker thread. + */ terminate () { this.child.kill("SIGINT"); } diff --git a/src/helpers.ts b/src/helpers.ts index e5eb40211..eff5fa731 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -83,6 +83,10 @@ export function canonicalJson(obj: any): string { } +/** + * Check for deep equality of two objects. + * Only arrays, objects and primitives are supported. + */ export function deepEquals(x: any, y: any): boolean { if (x === y) { return true; @@ -98,6 +102,10 @@ export function deepEquals(x: any, y: any): boolean { } +/** + * Map from a collection to a list or results and then + * concatenate the results. + */ export function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] { return xs.reduce((acc: U[], next: T) => [...f(next), ...acc], []); } diff --git a/src/logging.ts b/src/logging.ts index 19dd2f76c..a589c8091 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -15,9 +15,7 @@ */ /** - * Configurable logging. - * - * @author Florian Dold + * Configurable logging. Allows to log persistently to a database. */ import { @@ -26,8 +24,14 @@ import { openPromise, } from "./query"; +/** + * Supported log levels. + */ export type Level = "error" | "debug" | "info" | "warn"; +// Right now, our debug/info/warn/debug loggers just use the console based +// loggers. This might change in the future. + function makeInfo() { return console.info.bind(console, "%o"); } @@ -44,6 +48,9 @@ function makeDebug() { return console.log.bind(console, "%o"); } +/** + * Log a message using the configurable logger. + */ export async function log(msg: string, level: Level = "info"): Promise<void> { const ci = getCallInfo(2); return record(level, msg, undefined, ci.file, ci.line, ci.column); @@ -122,17 +129,50 @@ function parseStackLine(stackLine: string): Frame { let db: IDBDatabase|undefined; +/** + * A structured log entry as stored in the database. + */ export interface LogEntry { + /** + * Soure code column where the error occured. + */ col?: number; + /** + * Additional detail for the log statement. + */ detail?: string; + /** + * Id of the log entry, used as primary + * key for the database. + */ id?: number; + /** + * Log level, see [[Level}}. + */ level: string; + /** + * Line where the log was created from. + */ line?: number; + /** + * The actual log message. + */ msg: string; + /** + * The source file where the log enctry + * was created from. + */ source?: string; + /** + * Time when the log entry was created. + */ timestamp: number; } +/** + * Get all logs. Only use for debugging, since this returns all logs ever made + * at once without pagination. + */ export async function getLogs(): Promise<LogEntry[]> { if (!db) { db = await openLoggingDb(); @@ -147,6 +187,9 @@ export async function getLogs(): Promise<LogEntry[]> { */ let barrier: any; +/** + * Record an exeption in the log. + */ export async function recordException(msg: string, e: any): Promise<void> { let stack: string|undefined; let frame: Frame|undefined; @@ -165,6 +208,9 @@ export async function recordException(msg: string, e: any): Promise<void> { return record("error", e.toString(), stack, frame.file, frame.line, frame.column); } +/** + * Record a log entry in the database. + */ export async function record(level: Level, msg: string, detail?: string, @@ -215,6 +261,10 @@ const loggingDbVersion = 1; const logsStore: Store<LogEntry> = new Store<LogEntry>("logs"); +/** + * Get a handle to the IndexedDB used to store + * logs. + */ export function openLoggingDb(): Promise<IDBDatabase> { return new Promise<IDBDatabase>((resolve, reject) => { const req = indexedDB.open("taler-logging", loggingDbVersion); @@ -238,7 +288,22 @@ export function openLoggingDb(): Promise<IDBDatabase> { }); } +/** + * Log a message at severity info. + */ export const info = makeInfo(); + +/** + * Log a message at severity debug. + */ export const debug = makeDebug(); + +/** + * Log a message at severity warn. + */ export const warn = makeWarn(); + +/** + * Log a message at severity error. + */ export const error = makeError(); diff --git a/src/query.ts b/src/query.ts index 78b810371..cb033df4c 100644 --- a/src/query.ts +++ b/src/query.ts @@ -53,6 +53,9 @@ export class Store<T> { * Definition of an index. */ export class Index<S extends IDBValidKey, T> { + /** + * Name of the store that this index is associated with. + */ storeName: string; constructor(s: Store<T>, public indexName: string, public keyPath: string | string[]) { @@ -127,16 +130,26 @@ export interface QueryStream<T> { * Query result that consists of at most one value. */ export interface QueryValue<T> { + /** + * Apply a function to a query value. + */ map<S>(f: (x: T) => S): QueryValue<S>; + /** + * Conditionally execute either of two queries based + * on a property of this query value. + * + * Useful to properly implement complex queries within a transaction (as + * opposed to just computing the conditional and then executing either + * branch). This is necessary since IndexedDB does not allow long-lived + * transactions. + */ cond<R>(f: (x: T) => boolean, onTrue: (r: QueryRoot) => R, onFalse: (r: QueryRoot) => R): Promise<void>; } abstract class BaseQueryValue<T> implements QueryValue<T> { - root: QueryRoot; - constructor(root: QueryRoot) { - this.root = root; + constructor(public root: QueryRoot) { } map<S>(f: (x: T) => S): QueryValue<S> { @@ -160,8 +173,9 @@ abstract class BaseQueryValue<T> implements QueryValue<T> { } class FirstQueryValue<T> extends BaseQueryValue<T> { - gotValue = false; - s: QueryStreamBase<T>; + private gotValue = false; + private s: QueryStreamBase<T>; + constructor(stream: QueryStreamBase<T>) { super(stream.root); this.s = stream; @@ -183,13 +197,8 @@ class FirstQueryValue<T> extends BaseQueryValue<T> { } class MapQueryValue<T, S> extends BaseQueryValue<S> { - mapFn: (x: T) => S; - v: BaseQueryValue<T>; - - constructor(v: BaseQueryValue<T>, mapFn: (x: T) => S) { + constructor(private v: BaseQueryValue<T>, private mapFn: (x: T) => S) { super(v.root); - this.v = v; - this.mapFn = mapFn; } subscribeOne(f: SubscribeOneFn): void { @@ -226,11 +235,7 @@ abstract class QueryStreamBase<T> implements QueryStream<T>, PromiseLike<void> { abstract subscribe(f: (isDone: boolean, value: any, tx: IDBTransaction) => void): void; - - root: QueryRoot; - - constructor(root: QueryRoot) { - this.root = root; + constructor(public root: QueryRoot) { } first(): QueryValue<T> { @@ -313,13 +318,8 @@ type SubscribeOneFn = (value: any, tx: IDBTransaction) => void; type FlatMapFn<T> = (v: T) => T[]; class QueryStreamFilter<T> extends QueryStreamBase<T> { - s: QueryStreamBase<T>; - filterFn: FilterFn; - - constructor(s: QueryStreamBase<T>, filterFn: FilterFn) { + constructor(public s: QueryStreamBase<T>, public filterFn: FilterFn) { super(s.root); - this.s = s; - this.filterFn = filterFn; } subscribe(f: SubscribeFn) { @@ -337,13 +337,8 @@ class QueryStreamFilter<T> extends QueryStreamBase<T> { class QueryStreamFlatMap<T, S> extends QueryStreamBase<S> { - s: QueryStreamBase<T>; - flatMapFn: (v: T) => S[]; - - constructor(s: QueryStreamBase<T>, flatMapFn: (v: T) => S[]) { + constructor(public s: QueryStreamBase<T>, public flatMapFn: (v: T) => S[]) { super(s.root); - this.s = s; - this.flatMapFn = flatMapFn; } subscribe(f: SubscribeFn) { @@ -362,13 +357,8 @@ class QueryStreamFlatMap<T, S> extends QueryStreamBase<S> { class QueryStreamMap<S, T> extends QueryStreamBase<T> { - s: QueryStreamBase<S>; - mapFn: (v: S) => T; - - constructor(s: QueryStreamBase<S>, mapFn: (v: S) => T) { + constructor(public s: QueryStreamBase<S>, public mapFn: (v: S) => T) { super(s.root); - this.s = s; - this.mapFn = mapFn; } subscribe(f: SubscribeFn) { @@ -385,18 +375,9 @@ class QueryStreamMap<S, T> extends QueryStreamBase<T> { class QueryStreamIndexJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> { - s: QueryStreamBase<T>; - storeName: string; - key: any; - indexName: string; - - constructor(s: QueryStreamBase<T>, storeName: string, indexName: string, - key: any) { + constructor(public s: QueryStreamBase<T>, public storeName: string, public indexName: string, + public key: any) { super(s.root); - this.s = s; - this.storeName = storeName; - this.key = key; - this.indexName = indexName; } subscribe(f: SubscribeFn) { @@ -420,18 +401,9 @@ class QueryStreamIndexJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> { class QueryStreamIndexJoinLeft<T, S> extends QueryStreamBase<JoinLeftResult<T, S>> { - s: QueryStreamBase<T>; - storeName: string; - key: any; - indexName: string; - - constructor(s: QueryStreamBase<T>, storeName: string, indexName: string, - key: any) { + constructor(public s: QueryStreamBase<T>, public storeName: string, public indexName: string, + public key: any) { super(s.root); - this.s = s; - this.storeName = storeName; - this.key = key; - this.indexName = indexName; } subscribe(f: SubscribeFn) { @@ -461,16 +433,9 @@ class QueryStreamIndexJoinLeft<T, S> extends QueryStreamBase<JoinLeftResult<T, S class QueryStreamKeyJoin<T, S> extends QueryStreamBase<JoinResult<T, S>> { - s: QueryStreamBase<T>; - storeName: string; - key: any; - - constructor(s: QueryStreamBase<T>, storeName: string, - key: any) { + constructor(public s: QueryStreamBase<T>, public storeName: string, + public key: any) { super(s.root); - this.s = s; - this.storeName = storeName; - this.key = key; } subscribe(f: SubscribeFn) { diff --git a/src/types.ts b/src/types.ts index 91a61bc4b..805a0c061 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,32 +28,77 @@ */ import { Checkable } from "./checkable"; +/** + * Non-negative financial amount. Fractional values are expressed as multiples + * of 1e-8. + */ @Checkable.Class() export class AmountJson { + /** + * Value, must be an integer. + */ @Checkable.Number - value: number; + readonly value: number; + /** + * Fraction, must be an integer. Represent 1/1e8 of a unit. + */ @Checkable.Number - fraction: number; + readonly fraction: number; + /** + * Currency of the amount. + */ @Checkable.String - currency: string; + readonly currency: string; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => AmountJson; } +/** + * Amount with a sign. + */ export interface SignedAmountJson { + /** + * The absolute amount. + */ amount: AmountJson; + /** + * Sign. + */ isNegative: boolean; } +/** + * A reserve record as stored in the wallet's database. + */ export interface ReserveRecord { + /** + * The reserve public key. + */ reserve_pub: string; + /** + * The reserve private key. + */ reserve_priv: string; + /** + * The exchange base URL. + */ exchange_base_url: string; + /** + * Time when the reserve was created. + */ created: number; + /** + * Time when the reserve was last queried, + * or 'null' if it was never queried. + */ last_query: number | null; /** * Current amount left in the reserve @@ -65,17 +110,16 @@ export interface ReserveRecord { * be higher than the requested_amount */ requested_amount: AmountJson; - - /** * What's the current amount that sits * in precoins? */ precoin_amount: AmountJson; - - + /** + * The bank conformed that the reserve will eventually + * be filled with money. + */ confirmed: boolean; - /** * We got some payback to this reserve. We'll cease to automatically * withdraw money from it. @@ -106,6 +150,9 @@ export interface CurrencyRecord { } +/** + * Response for the create reserve request to the wallet. + */ @Checkable.Class() export class CreateReserveResponse { /** @@ -115,52 +162,114 @@ export class CreateReserveResponse { @Checkable.String exchange: string; + /** + * Reserve public key of the newly created reserve. + */ @Checkable.String reservePub: string; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => CreateReserveResponse; } + +/** + * Status of a denomination. + */ export enum DenominationStatus { + /** + * Verification was delayed. + */ Unverified, + /** + * Verified as valid. + */ VerifiedGood, + /** + * Verified as invalid. + */ VerifiedBad, } +/** + * Denomination record as stored in the wallet's database. + */ +@Checkable.Class() export class DenominationRecord { + /** + * Value of one coin of the denomination. + */ @Checkable.Value(AmountJson) value: AmountJson; + /** + * The denomination public key. + */ @Checkable.String denomPub: string; + /** + * Hash of the denomination public key. + * Stored in the database for faster lookups. + */ @Checkable.String denomPubHash: string; + /** + * Fee for withdrawing. + */ @Checkable.Value(AmountJson) feeWithdraw: AmountJson; + /** + * Fee for depositing. + */ @Checkable.Value(AmountJson) feeDeposit: AmountJson; + /** + * Fee for refreshing. + */ @Checkable.Value(AmountJson) feeRefresh: AmountJson; + /** + * Fee for refunding. + */ @Checkable.Value(AmountJson) feeRefund: AmountJson; + /** + * Validity start date of the denomination. + */ @Checkable.String stampStart: string; + /** + * Date after which the currency can't be withdrawn anymore. + */ @Checkable.String stampExpireWithdraw: string; + /** + * Date after the denomination officially doesn't exist anymore. + */ @Checkable.String stampExpireLegal: string; + /** + * Data after which coins of this denomination can't be deposited anymore. + */ @Checkable.String stampExpireDeposit: string; + /** + * Signature by the exchange's master key over the denomination + * information. + */ @Checkable.String masterSig: string; @@ -178,9 +287,16 @@ export class DenominationRecord { @Checkable.Boolean isOffered: boolean; + /** + * Base URL of the exchange. + */ @Checkable.String exchangeBaseUrl: string; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => Denomination; } @@ -189,59 +305,124 @@ export class DenominationRecord { */ @Checkable.Class() export class Denomination { + /** + * Value of one coin of the denomination. + */ @Checkable.Value(AmountJson) value: AmountJson; + /** + * Public signing key of the denomination. + */ @Checkable.String denom_pub: string; + /** + * Fee for withdrawing. + */ @Checkable.Value(AmountJson) fee_withdraw: AmountJson; + /** + * Fee for depositing. + */ @Checkable.Value(AmountJson) fee_deposit: AmountJson; + /** + * Fee for refreshing. + */ @Checkable.Value(AmountJson) fee_refresh: AmountJson; + /** + * Fee for refunding. + */ @Checkable.Value(AmountJson) fee_refund: AmountJson; + /** + * Start date from which withdraw is allowed. + */ @Checkable.String stamp_start: string; + /** + * End date for withdrawing. + */ @Checkable.String stamp_expire_withdraw: string; + /** + * Expiration date after which the exchange can forget about + * the currency. + */ @Checkable.String stamp_expire_legal: string; + /** + * Date after which the coins of this denomination can't be + * deposited anymore. + */ @Checkable.String stamp_expire_deposit: string; + /** + * Signature over the denomination information by the exchange's master + * signing key. + */ @Checkable.String master_sig: string; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => Denomination; } +/** + * Auditor information. + */ export interface Auditor { - // official name + /** + * Official name. + */ name: string; - // Auditor's public key + /** + * Auditor's public key. + */ auditor_pub: string; - // Base URL of the auditor + /** + * Base URL of the auditor. + */ url: string; } +/** + * Exchange record as stored in the wallet's database. + */ export interface ExchangeRecord { + /** + * Base url of the exchange. + */ baseUrl: string; + /** + * Master public key of the exchange. + */ masterPublicKey: string; + /** + * Auditors (partially) auditing the exchange. + */ auditors: Auditor[]; + + /** + * Currency that the exchange offers. + */ currency: string; /** @@ -282,14 +463,36 @@ export interface PreCoinRecord { coinValue: AmountJson; } +/** + * Planchet for a coin during refrehs. + */ export interface RefreshPreCoinRecord { + /** + * Public key for the coin. + */ publicKey: string; + /** + * Private key for the coin. + */ privateKey: string; + /** + * Blinded public key. + */ coinEv: string; + /** + * Blinding key used. + */ blindingKey: string; } +/** + * Request that we send to the exchange to get a payback. + */ export interface PaybackRequest { + /** + * Denomination public key of the coin we want to get + * paid back. + */ denom_pub: string; /** @@ -297,13 +500,26 @@ export interface PaybackRequest { */ denom_sig: string; + /** + * Coin public key of the coin we want to refund. + */ coin_pub: string; + /** + * Blinding key that was used during withdraw, + * used to prove that we were actually withdrawing the coin. + */ coin_blind_key_secret: string; + /** + * Signature made by the coin, authorizing the payback. + */ coin_sig: string; } +/** + * Response that we get from the exchange for a payback request. + */ @Checkable.Class() export class PaybackConfirmation { /** @@ -344,6 +560,10 @@ export class PaybackConfirmation { @Checkable.String exchange_pub: string; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => PaybackConfirmation; } @@ -378,15 +598,19 @@ export interface RefreshSessionRecord { */ newDenoms: string[]; - + /** + * Precoins for each cut-and-choose instance. + */ preCoinsForGammas: RefreshPreCoinRecord[][]; - /** * The transfer keys, kappa of them. */ transferPubs: string[]; + /** + * Private keys for the transfer public keys. + */ transferPrivs: string[]; /** @@ -399,23 +623,73 @@ export interface RefreshSessionRecord { */ hash: string; + /** + * Base URL for the exchange we're doing the refresh with. + */ exchangeBaseUrl: string; + /** + * Is this session finished? + */ finished: boolean; } +/** + * Deposit permission for a single coin. + */ export interface CoinPaySig { + /** + * Signature by the coin. + */ coin_sig: string; + /** + * Public key of the coin being spend. + */ coin_pub: string; + /** + * Signature made by the denomination public key. + */ ub_sig: string; + /** + * The denomination public key associated with this coin. + */ denom_pub: string; + /** + * The amount that is subtracted from this coin with this payment. + */ f: AmountJson; } +/** + * Status of a coin. + */ export enum CoinStatus { - Fresh, TransactionPending, Dirty, Refreshed, PaybackPending, PaybackDone, + /** + * Withdrawn and never shown to anybody. + */ + Fresh, + /** + * Currently planned to be sent to a merchant for a transaction. + */ + TransactionPending, + /** + * Used for a completed transaction and now dirty. + */ + Dirty, + /** + * A coin that was refreshed. + */ + Refreshed, + /** + * Coin marked to be paid back, but payback not finished. + */ + PaybackPending, + /** + * Coin fully paid back. + */ + PaybackDone, } @@ -462,6 +736,10 @@ export interface CoinRecord { */ suspended?: boolean; + /** + * Blinding key used when withdrawing the coin. + * Potentionally sed again during payback. + */ blindingKey: string; /** @@ -477,29 +755,70 @@ export interface CoinRecord { } +/** + * Information about an exchange as stored inside a + * merchant's contract terms. + */ @Checkable.Class() export class ExchangeHandle { + /** + * Master public signing key of the exchange. + */ @Checkable.String master_pub: string; + /** + * Base URL of the exchange. + */ @Checkable.String url: string; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => ExchangeHandle; } + +/** + * Mapping from currency names to detailed balance + * information for that particular currency. + */ export interface WalletBalance { + /** + * Mapping from currency name to defailed balance info. + */ [currency: string]: WalletBalanceEntry; -} +}; + +/** + * Detailed wallet balance for a particular currency. + */ export interface WalletBalanceEntry { + /** + * Directly available amount. + */ available: AmountJson; + /** + * Amount that we're waiting for (refresh, withdrawal). + */ pendingIncoming: AmountJson; + /** + * Amount that's marked for a pending payment. + */ pendingPayment: AmountJson; + /** + * Amount that was paid back and we could withdraw again. + */ paybackAmount: AmountJson; } +/** + * Information about a merchant. + */ interface Merchant { /** * label for a location with the business address of the merchant @@ -524,108 +843,235 @@ interface Merchant { instance?: string; } + +/** + * Contract terms from a merchant. + */ @Checkable.Class({validate: true}) export class Contract { - - validate() { + private validate() { if (this.exchanges.length === 0) { throw Error("no exchanges in contract"); } } + /** + * Hash of the merchant's wire details. + */ @Checkable.String H_wire: string; + /** + * Wire method the merchant wants to use. + */ @Checkable.String wire_method: string; + /** + * Human-readable short summary of the contract. + */ @Checkable.Optional(Checkable.String) summary?: string; + /** + * Nonce used to ensure freshness. + */ @Checkable.Optional(Checkable.String) nonce?: string; + /** + * Total amount payable. + */ @Checkable.Value(AmountJson) amount: AmountJson; + /** + * Auditors accepted by the merchant. + */ @Checkable.List(Checkable.AnyObject) auditors: any[]; + /** + * Deadline to pay for the contract. + */ @Checkable.Optional(Checkable.String) pay_deadline: string; + /** + * Delivery locations. + */ @Checkable.Any locations: any; + /** + * Maximum deposit fee covered by the merchant. + */ @Checkable.Value(AmountJson) max_fee: AmountJson; + /** + * Information about the merchant. + */ @Checkable.Any merchant: any; + /** + * Public key of the merchant. + */ @Checkable.String merchant_pub: string; + /** + * List of accepted exchanges. + */ @Checkable.List(Checkable.Value(ExchangeHandle)) exchanges: ExchangeHandle[]; + /** + * Products that are sold in this contract. + */ @Checkable.List(Checkable.AnyObject) products: any[]; + /** + * Deadline for refunds. + */ @Checkable.String refund_deadline: string; + /** + * Time when the contract was generated by the merchant. + */ @Checkable.String timestamp: string; + /** + * Order id to uniquely identify the purchase within + * one merchant instance. + */ @Checkable.String order_id: string; + /** + * URL to post the payment to. + */ @Checkable.String pay_url: string; + /** + * Fulfillment URL to view the product or + * delivery status. + */ @Checkable.String fulfillment_url: string; + /** + * Share of the wire fee that must be settled with one payment. + */ @Checkable.Optional(Checkable.Number) wire_fee_amortization?: number; + /** + * Maximum wire fee that the merchant agrees to pay for. + */ @Checkable.Optional(Checkable.Value(AmountJson)) max_wire_fee?: AmountJson; + /** + * Extra data, interpreted by the mechant only. + */ @Checkable.Any extra: any; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => Contract; } +/** + * Wire fee for one wire method as stored in the + * wallet's database. + */ export interface WireFee { + /** + * Fee for wire transfers. + */ wireFee: AmountJson; + + /** + * Fees to close and refund a reserve. + */ closingFee: AmountJson; + + /** + * Start date of the fee. + */ startStamp: number; + + /** + * End date of the fee. + */ endStamp: number; + + /** + * Signature made by the exchange master key. + */ sig: string; } + +/** + * Wire fees for an exchange. + */ export interface ExchangeWireFeesRecord { + /** + * Base URL of the exchange. + */ exchangeBaseUrl: string; - feesForType: { [type: string]: WireFee[] }; + + /** + * Mapping from wire method type to the wire fee. + */ + feesForType: { [wireMethod: string]: WireFee[] }; } +/** + * Coins used for a payment, with signatures authorizing the payment and the + * coins with remaining value updated to accomodate for a payment. + */ export type PayCoinInfo = Array<{ updatedCoin: CoinRecord, sig: CoinPaySig }>; +/** + * Amount helpers. + */ export namespace Amounts { + /** + * Number of fractional units that one value unit represents. + */ export const fractionalBase = 1e8; + /** + * Result of a possibly overflowing operation. + */ export interface Result { + /** + * Resulting, possibly saturated amount. + */ amount: AmountJson; - // Was there an over-/underflow? + /** + * Was there an over-/underflow? + */ saturated: boolean; } + /** + * Get the largest amount that is safely representable. + */ export function getMaxAmount(currency: string): AmountJson { return { currency, @@ -634,6 +1080,9 @@ export namespace Amounts { }; } + /** + * Get an amount that represents zero units of a currency. + */ export function getZero(currency: string): AmountJson { return { currency, @@ -642,6 +1091,13 @@ export namespace Amounts { }; } + /** + * Add two amounts. Return the result and whether + * the addition overflowed. The overflow is always handled + * by saturating and never by wrapping. + * + * Throws when currencies don't match. + */ export function add(first: AmountJson, ...rest: AmountJson[]): Result { const currency = first.currency; let value = first.value + Math.floor(first.fraction / fractionalBase); @@ -663,7 +1119,13 @@ export namespace Amounts { return { amount: { currency, value, fraction }, saturated: false }; } - + /** + * Subtract two amounts. Return the result and whether + * the subtraction overflowed. The overflow is always handled + * by saturating and never by wrapping. + * + * Throws when currencies don't match. + */ export function sub(a: AmountJson, ...rest: AmountJson[]): Result { const currency = a.currency; let value = a.value; @@ -691,6 +1153,10 @@ export namespace Amounts { return { amount: { currency, value, fraction }, saturated: false }; } + /** + * Compare two amounts. Returns 0 when equal, -1 when a < b + * and +1 when a > b. Throws when currencies don't match. + */ export function cmp(a: AmountJson, b: AmountJson): number { if (a.currency !== b.currency) { throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`); @@ -715,6 +1181,9 @@ export namespace Amounts { } } + /** + * Create a copy of an amount. + */ export function copy(a: AmountJson): AmountJson { return { currency: a.currency, @@ -723,6 +1192,9 @@ export namespace Amounts { }; } + /** + * Divide an amount. Throws on division by zero. + */ export function divide(a: AmountJson, n: number): AmountJson { if (n === 0) { throw Error(`Division by 0`); @@ -738,7 +1210,10 @@ export namespace Amounts { }; } - export function isNonZero(a: AmountJson) { + /** + * Check if an amount is non-zero. + */ + export function isNonZero(a: AmountJson): boolean { return a.value > 0 || a.fraction > 0; } @@ -759,7 +1234,13 @@ export namespace Amounts { } +/** + * Listener for notifications from the wallet. + */ export interface Notifier { + /** + * Called when a new notification arrives. + */ notify(): void; } diff --git a/src/wallet-test.ts b/src/wallet-test.ts index 51a8497b7..acd776d67 100644 --- a/src/wallet-test.ts +++ b/src/wallet-test.ts @@ -69,7 +69,7 @@ test("coin selection 1", (t) => { fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"), ]; - const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.1")); + const res = wallet.selectPayCoins(cds, a("EUR:2.0"), a("EUR:0.1")); if (!res) { t.fail(); return; @@ -86,7 +86,7 @@ test("coin selection 2", (t) => { // Merchant covers the fee, this one shouldn't be used fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"), ]; - const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5")); + const res = wallet.selectPayCoins(cds, a("EUR:2.0"), a("EUR:0.5")); if (!res) { t.fail(); return; @@ -103,7 +103,7 @@ test("coin selection 3", (t) => { // this coin should be selected instead of previous one with fee fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.0"), ]; - const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.5")); + const res = wallet.selectPayCoins(cds, a("EUR:2.0"), a("EUR:0.5")); if (!res) { t.fail(); return; @@ -119,7 +119,7 @@ test("coin selection 4", (t) => { fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), ]; - const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2")); + const res = wallet.selectPayCoins(cds, a("EUR:2.0"), a("EUR:0.2")); if (!res) { t.fail(); return; @@ -135,7 +135,7 @@ test("coin selection 5", (t) => { fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), ]; - const res = wallet.selectCoins(cds, a("EUR:4.0"), a("EUR:0.2")); + const res = wallet.selectPayCoins(cds, a("EUR:4.0"), a("EUR:0.2")); t.true(!res); t.pass(); }); @@ -146,7 +146,7 @@ test("coin selection 6", (t) => { fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), fakeCwd("EUR:1.0", "EUR:1.0", "EUR:0.5"), ]; - const res = wallet.selectCoins(cds, a("EUR:2.0"), a("EUR:0.2")); + const res = wallet.selectPayCoins(cds, a("EUR:2.0"), a("EUR:0.2")); t.true(!res); t.pass(); }); diff --git a/src/wallet.ts b/src/wallet.ts index b21cdbd96..743042b97 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -81,7 +81,14 @@ import URI = require("urijs"); * Named tuple of coin and denomination. */ export interface CoinWithDenom { + /** + * A coin. Must have the same denomination public key as the associated + * denomination. + */ coin: CoinRecord; + /** + * An associated denomination. + */ denom: DenominationRecord; } @@ -92,6 +99,9 @@ export interface CoinWithDenom { */ @Checkable.Class() export class Payback { + /** + * The hash of the denomination public key for which the payback is offered. + */ @Checkable.String h_denom_pub: string; } @@ -102,67 +112,123 @@ export class Payback { */ @Checkable.Class({extra: true}) export class KeysJson { + /** + * List of offered denominations. + */ @Checkable.List(Checkable.Value(Denomination)) denoms: Denomination[]; + /** + * The exchange's master public key. + */ @Checkable.String master_public_key: string; + /** + * The list of auditors (partially) auditing the exchange. + */ @Checkable.Any auditors: any[]; + /** + * Timestamp when this response was issued. + */ @Checkable.String list_issue_date: string; + /** + * List of paybacks for compromised denominations. + */ @Checkable.List(Checkable.Value(Payback)) payback?: Payback[]; + /** + * Short-lived signing keys used to sign online + * responses. + */ @Checkable.Any signkeys: any; - @Checkable.String - eddsa_pub: string; - - @Checkable.String - eddsa_sig: string; - + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => KeysJson; } +/** + * Wire fees as anounced by the exchange. + */ @Checkable.Class() class WireFeesJson { + /** + * Cost of a wire transfer. + */ @Checkable.Value(AmountJson) wire_fee: AmountJson; + /** + * Cost of clising a reserve. + */ @Checkable.Value(AmountJson) closing_fee: AmountJson; + /** + * Signature made with the exchange's master key. + */ @Checkable.String sig: string; + /** + * Date from which the fee applies. + */ @Checkable.String start_date: string; + /** + * Data after which the fee doesn't apply anymore. + */ @Checkable.String end_date: string; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => WireFeesJson; } +/** + * Information about wire transfer methods supported + * by the exchange. + */ @Checkable.Class({extra: true}) class WireDetailJson { + /** + * Name of the wire transfer method. + */ @Checkable.String type: string; + /** + * Fees associated with the wire transfer method. + */ @Checkable.List(Checkable.Value(WireFeesJson)) fees: WireFeesJson[]; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => WireDetailJson; } +/** + * Request to mark a reserve as confirmed. + */ @Checkable.Class() export class CreateReserveRequest { /** @@ -177,10 +243,17 @@ export class CreateReserveRequest { @Checkable.String exchange: string; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => CreateReserveRequest; } +/** + * Request to mark a reserve as confirmed. + */ @Checkable.Class() export class ConfirmReserveRequest { /** @@ -190,21 +263,40 @@ export class ConfirmReserveRequest { @Checkable.String reservePub: string; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => ConfirmReserveRequest; } +/** + * Offer record, stored in the wallet's database. + */ @Checkable.Class() export class OfferRecord { + /** + * The contract that was offered by the merchant. + */ @Checkable.Value(Contract) contract: Contract; + /** + * Signature by the merchant over the contract details. + */ @Checkable.String merchant_sig: string; + /** + * Hash of the contract terms. + */ @Checkable.String H_contract: string; + /** + * Time when the offer was made. + */ @Checkable.Number offer_time: number; @@ -214,14 +306,41 @@ export class OfferRecord { @Checkable.Optional(Checkable.Number) id?: number; + /** + * Verify that a value matches the schema of this class and convert it into a + * member. + */ static checked: (obj: any) => OfferRecord; } +/** + * Activity history record. + */ export interface HistoryRecord { + /** + * Type of the history event. + */ type: string; + + /** + * Time when the activity was recorded. + */ timestamp: number; + + /** + * Subject of the entry. Used to group multiple history records together. + * Only the latest history record with the same subjectId will be shown. + */ subjectId?: string; + + /** + * Details used when rendering the history record. + */ detail: any; + + /** + * Level of detail of the history entry. + */ level: HistoryLevel; } @@ -246,6 +365,11 @@ interface TransactionRecord { finished: boolean; } + +/** + * Level of detail at which a history + * entry should be shown. + */ export enum HistoryLevel { Trace = 1, Developer = 2, @@ -254,19 +378,34 @@ export enum HistoryLevel { } +/** + * Badge that shows activity for the wallet. + */ export interface Badge { - setText(s: string): void; - setColor(c: string): void; + /** + * Start indicating background activity. + */ startBusy(): void; + + /** + * Stop indicating background activity. + */ stopBusy(): void; } + +/** + * Nonce record as stored in the wallet's database. + */ export interface NonceRecord { priv: string; pub: string; } - +/** + * Configuration key/value entries to configure + * the wallet. + */ export interface ConfigRecord { key: string; value: any; @@ -328,10 +467,17 @@ function isWithdrawableDenom(d: DenominationRecord) { } +/** + * Result of selecting coins, contains the exchange, and selected + * coins with their denomination. + */ export type CoinSelectionResult = {exchangeUrl: string, cds: CoinWithDenom[]}|undefined; -export function selectCoins(cds: CoinWithDenom[], paymentAmount: AmountJson, - depositFeeLimit: AmountJson): CoinWithDenom[]|undefined { +/** + * Select coins for a payment under the merchant's constraints. + */ +export function selectPayCoins(cds: CoinWithDenom[], paymentAmount: AmountJson, + depositFeeLimit: AmountJson): CoinWithDenom[]|undefined { if (cds.length === 0) { return undefined; } @@ -406,7 +552,11 @@ function getWithdrawDenomList(amountAvailable: AmountJson, return ds; } +/* tslint:disable:completed-docs */ +/** + * The stores and indices for the wallet database. + */ export namespace Stores { class ExchangeStore extends Store<ExchangeRecord> { constructor() { @@ -489,6 +639,7 @@ export namespace Stores { super("exchangeWireFees", {keyPath: "exchangeBaseUrl"}); } } + export const exchanges = new ExchangeStore(); export const exchangeWireFees = new ExchangeWireFeesStore(); export const nonces = new NonceStore(); @@ -504,6 +655,8 @@ export namespace Stores { export const config = new ConfigStore(); } +/* tslint:enable:completed-docs */ + interface CoinsForPaymentArgs { allowedAuditors: Auditor[]; @@ -517,13 +670,15 @@ interface CoinsForPaymentArgs { } +/** + * The platform-independent wallet implementation. + */ export class Wallet { private db: IDBDatabase; private http: HttpRequestLibrary; private badge: Badge; private notifier: Notifier; - public cryptoApi: CryptoApi; - + private cryptoApi: CryptoApi; private processPreCoinConcurrent = 0; private processPreCoinThrottle: {[url: string]: number} = {}; @@ -748,7 +903,7 @@ export class Wallet { } } - const res = selectCoins(cds, remainingAmount, depositFeeLimit); + const res = selectPayCoins(cds, remainingAmount, depositFeeLimit); if (res) { return { cds: res, diff --git a/src/wxApi.ts b/src/wxApi.ts index 1f7d08fb3..8a95e75f5 100644 --- a/src/wxApi.ts +++ b/src/wxApi.ts @@ -14,6 +14,14 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +/** + * Interface to the wallet through WebExtension messaging. + */ + + +/** + * Imports. + */ import { AmountJson, CoinRecord, @@ -25,12 +33,11 @@ import { ReserveRecord, } from "./types"; + /** - * Interface to the wallet through WebExtension messaging. - * @author Florian Dold + * Query the wallet for the coins that would be used to withdraw + * from a given reserve. */ - - export function getReserveCreationInfo(baseUrl: string, amount: AmountJson): Promise<ReserveCreationInfo> { const m = { type: "reserve-creation-info", detail: { baseUrl, amount } }; @@ -48,7 +55,8 @@ export function getReserveCreationInfo(baseUrl: string, }); } -export async function callBackend(type: string, detail?: any): Promise<any> { + +async function callBackend(type: string, detail?: any): Promise<any> { return new Promise<any>((resolve, reject) => { chrome.runtime.sendMessage({ type, detail }, (resp) => { if (resp && resp.error) { @@ -60,55 +68,107 @@ export async function callBackend(type: string, detail?: any): Promise<any> { }); } + +/** + * Get all exchanges the wallet knows about. + */ export async function getExchanges(): Promise<ExchangeRecord[]> { return await callBackend("get-exchanges"); } + +/** + * Get all currencies the exchange knows about. + */ export async function getCurrencies(): Promise<CurrencyRecord[]> { return await callBackend("get-currencies"); } +/** + * Get information about a specific currency. + */ export async function getCurrency(name: string): Promise<CurrencyRecord|null> { return await callBackend("currency-info", {name}); } + +/** + * Get information about a specific exchange. + */ export async function getExchangeInfo(baseUrl: string): Promise<ExchangeRecord> { return await callBackend("exchange-info", {baseUrl}); } + +/** + * Replace an existing currency record with the one given. The currency to + * replace is specified inside the currency record. + */ export async function updateCurrency(currencyRecord: CurrencyRecord): Promise<void> { return await callBackend("update-currency", { currencyRecord }); } + +/** + * Get all reserves the wallet has at an exchange. + */ export async function getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> { return await callBackend("get-reserves", { exchangeBaseUrl }); } + +/** + * Get all reserves for which a payback is available. + */ export async function getPaybackReserves(): Promise<ReserveRecord[]> { return await callBackend("get-payback-reserves"); } + +/** + * Withdraw the payback that is available for a reserve. + */ export async function withdrawPaybackReserve(reservePub: string): Promise<ReserveRecord[]> { return await callBackend("withdraw-payback-reserve", { reservePub }); } + +/** + * Get all coins withdrawn from the given exchange. + */ export async function getCoins(exchangeBaseUrl: string): Promise<CoinRecord[]> { return await callBackend("get-coins", { exchangeBaseUrl }); } + +/** + * Get all precoins withdrawn from the given exchange. + */ export async function getPreCoins(exchangeBaseUrl: string): Promise<PreCoinRecord[]> { return await callBackend("get-precoins", { exchangeBaseUrl }); } + +/** + * Get all denoms offered by the given exchange. + */ export async function getDenoms(exchangeBaseUrl: string): Promise<DenominationRecord[]> { return await callBackend("get-denoms", { exchangeBaseUrl }); } + +/** + * Start refreshing a coin. + */ export async function refresh(coinPub: string): Promise<void> { return await callBackend("refresh-coin", { coinPub }); } + +/** + * Request payback for a coin. Only works for non-refreshed coins. + */ export async function payback(coinPub: string): Promise<void> { return await callBackend("payback-coin", { coinPub }); } diff --git a/src/wxBackend.ts b/src/wxBackend.ts index 6b9601572..a9a208dcd 100644 --- a/src/wxBackend.ts +++ b/src/wxBackend.ts @@ -340,8 +340,9 @@ async function dispatch(handlers: any, req: any, sender: any, sendResponse: any) } } + class ChromeNotifier implements Notifier { - ports: Port[] = []; + private ports: Port[] = []; constructor() { chrome.runtime.onConnect.addListener((port) => { @@ -483,6 +484,11 @@ function clearRateLimitCache() { rateLimitCache = {}; } +/** + * Main function to run for the WebExtension backend. + * + * Sets up all event handlers and other machinery. + */ export async function wxMain() { window.onerror = (m, source, lineno, colno, error) => { logging.record("error", m + error, undefined, source || "(unknown)", lineno || 0, colno || 0); |