From e3cc9c59bcc36eee8c3234574cfdfda3f5eea658 Mon Sep 17 00:00:00 2001
From: Florian Dold <florian.dold@gmail.com>
Date: Mon, 12 Sep 2016 17:41:12 +0200
Subject: [PATCH] stricter type checking

---
 content_scripts/notify.ts |  25 ++++---
 gulpfile.js               |   2 +
 lib/shopApi.ts            |   4 +-
 lib/wallet/query.ts       | 140 +++++++++++++++++++++-----------------
 lib/wallet/wallet.ts      |  50 +++++++-------
 lib/wallet/wxMessaging.ts |  28 +++++---
 tsconfig.json             |   4 +-
 7 files changed, 140 insertions(+), 113 deletions(-)

diff --git a/content_scripts/notify.ts b/content_scripts/notify.ts
index cbcc7e590..7e54f27d6 100644
--- a/content_scripts/notify.ts
+++ b/content_scripts/notify.ts
@@ -35,7 +35,7 @@ namespace TalerNotify {
    * Wallet-internal version of offerContractFrom, used for 402 payments.
    */
   function internalOfferContractFrom(url: string) {
-    function handle_contract(contract_wrapper) {
+    function handle_contract(contract_wrapper: any) {
       var cEvent = new CustomEvent("taler-confirm-contract", {
         detail: {
           contract_wrapper: contract_wrapper,
@@ -88,7 +88,7 @@ namespace TalerNotify {
      * Try to notify the wallet first, before we show a potentially
      * synchronous error message (such as an alert) or leave the page.
      */
-    function handleFailedPayment(status) {
+    function handleFailedPayment(status: any) {
       const msg = {
         type: "payment-failed",
         detail: {},
@@ -99,19 +99,22 @@ namespace TalerNotify {
     }
 
 
-    function handleResponse(evt) {
+    function handleResponse(evt: CustomEvent) {
       console.log("handling taler-notify-payment");
       // Payment timeout in ms.
       let timeout_ms = 1000;
       // Current request.
-      let r;
-      let timeoutHandle = null;
+      let r: XMLHttpRequest | null = null;
+      let timeoutHandle: number|null = null;
       function sendPay() {
         r = new XMLHttpRequest();
         r.open("post", payUrl);
         r.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
         r.send(JSON.stringify(evt.detail.payment));
         r.onload = function() {
+          if (!r) {
+            throw Error("invariant");
+          }
           switch (r.status) {
             case 200:
               window.location.href = subst(evt.detail.contract.fulfillment_url,
@@ -152,13 +155,17 @@ namespace TalerNotify {
     document.dispatchEvent(eve);
   }
 
-  function subst(url: string, H_contract) {
+  function subst(url: string, H_contract: string) {
     url = url.replace("${H_contract}", H_contract);
     url = url.replace("${$}", "$");
     return url;
   }
 
-  const handlers = [];
+  interface Handler {
+    type: string;
+    listener: (e: CustomEvent) => void;
+  }
+  const handlers: Handler[] = [];
 
   function init() {
     chrome.runtime.sendMessage({type: "ping"}, (resp) => {
@@ -197,9 +204,7 @@ namespace TalerNotify {
   init();
 
   function registerHandlers() {
-    const $ = (x) => document.getElementById(x);
-
-    function addHandler(type, listener) {
+    function addHandler(type: string, listener: (e: CustomEvent) => void) {
       document.addEventListener(type, listener);
       handlers.push({type, listener});
     }
diff --git a/gulpfile.js b/gulpfile.js
index c2f5319d2..00a82e1a7 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -125,6 +125,8 @@ const tsBaseArgs = {
   noLib: true,
   noImplicitReturns: true,
   noFallthroughCasesInSwitch: true,
+  strictNullChecks: true,
+  noImplicitAny: true,
 };
 
 
diff --git a/lib/shopApi.ts b/lib/shopApi.ts
index 8179ff14d..7cc37a9db 100644
--- a/lib/shopApi.ts
+++ b/lib/shopApi.ts
@@ -24,7 +24,7 @@
 
 
 
-function subst(url: string, H_contract) {
+function subst(url: string, H_contract: string) {
   url = url.replace("${H_contract}", H_contract);
   url = url.replace("${$}", "$");
   return url;
@@ -138,7 +138,7 @@ export function fetchPayment(H_contract: any, offering_url: any) {
  * Offer a contract to the wallet after
  * downloading it from the given URL.
  */
-function offerContractFrom(url) {
+function offerContractFrom(url: string) {
   var contract_request = new XMLHttpRequest();
   console.log("downloading contract from '" + url + "'");
   contract_request.open("GET", url, true);
diff --git a/lib/wallet/query.ts b/lib/wallet/query.ts
index 4eccb696b..c7420a3f7 100644
--- a/lib/wallet/query.ts
+++ b/lib/wallet/query.ts
@@ -24,7 +24,7 @@
 "use strict";
 
 
-export function Query(db) {
+export function Query(db: IDBDatabase) {
   return new QueryRoot(db);
 }
 
@@ -36,24 +36,27 @@ export interface QueryStream<T> {
   indexJoin<S>(storeName: string,
                indexName: string,
                keyFn: (obj: any) => any): QueryStream<[T,S]>;
-  filter(f: (any) => boolean): QueryStream<T>;
+  filter(f: (x: any) => boolean): QueryStream<T>;
   reduce<S>(f: (v: T, acc: S) => S, start?: S): Promise<S>;
-  flatMap(f: (T) => T[]): QueryStream<T>;
+  flatMap(f: (x: T) => T[]): QueryStream<T>;
 }
 
 
 /**
  * Get an unresolved promise together with its extracted resolve / reject
  * function.
- *
- * @returns {{resolve: any, reject: any, promise: Promise<T>}}
  */
 function openPromise<T>() {
-  let resolve, reject;
+  let resolve: ((value?: T | PromiseLike<T>) => void) | null = null;
+  let reject: ((reason?: any) => void) | null = null;
   const promise = new Promise<T>((res, rej) => {
     resolve = res;
     reject = rej;
   });
+  if (!(resolve && reject)) {
+    // Never happens, unless JS implementation is broken
+    throw Error();
+  }
   return {resolve, reject, promise};
 }
 
@@ -61,7 +64,7 @@ function openPromise<T>() {
 abstract class QueryStreamBase<T> implements QueryStream<T> {
   abstract subscribe(f: (isDone: boolean,
                          value: any,
-                         tx: IDBTransaction) => void);
+                         tx: IDBTransaction) => void): void;
 
   root: QueryRoot;
 
@@ -69,30 +72,28 @@ abstract class QueryStreamBase<T> implements QueryStream<T> {
     this.root = root;
   }
 
-  flatMap(f: (T) => T[]): QueryStream<T> {
+  flatMap(f: (x: T) => T[]): QueryStream<T> {
     return new QueryStreamFlatMap(this, f);
   }
 
   indexJoin<S>(storeName: string,
                indexName: string,
                key: any): QueryStream<[T,S]> {
-    this.root.addWork(null, storeName, false);
+    this.root.addStoreAccess(storeName, false);
     return new QueryStreamIndexJoin(this, storeName, indexName, key);
   }
 
-  filter(f: (any) => boolean): QueryStream<T> {
+  filter(f: (x: any) => boolean): QueryStream<T> {
     return new QueryStreamFilter(this, f);
   }
 
-  reduce(f, acc?): Promise<any> {
-    let leakedResolve;
-    let p = new Promise((resolve, reject) => {
-      leakedResolve = resolve;
-    });
+  reduce<A>(f: (x: any, acc?: A) => A, init?: A): Promise<any> {
+    let {resolve, promise} = openPromise();
+    let acc = init;
 
     this.subscribe((isDone, value) => {
       if (isDone) {
-        leakedResolve(acc);
+        resolve(acc);
         return;
       }
       acc = f(value, acc);
@@ -100,22 +101,28 @@ abstract class QueryStreamBase<T> implements QueryStream<T> {
 
     return Promise.resolve()
                   .then(() => this.root.finish())
-                  .then(() => p);
+                  .then(() => promise);
   }
 }
 
+type FilterFn = (e: any) => boolean;
+type SubscribeFn = (done: boolean, value: any, tx: IDBTransaction) => void;
+
+interface FlatMapFn<T> {
+  (v: T): T[];
+}
 
 class QueryStreamFilter<T> extends QueryStreamBase<T> {
   s: QueryStreamBase<T>;
-  filterFn;
+  filterFn: FilterFn;
 
-  constructor(s: QueryStreamBase<T>, filterFn) {
+  constructor(s: QueryStreamBase<T>, filterFn: FilterFn) {
     super(s.root);
     this.s = s;
     this.filterFn = filterFn;
   }
 
-  subscribe(f) {
+  subscribe(f: SubscribeFn) {
     this.s.subscribe((isDone, value, tx) => {
       if (isDone) {
         f(true, undefined, tx);
@@ -131,15 +138,15 @@ class QueryStreamFilter<T> extends QueryStreamBase<T> {
 
 class QueryStreamFlatMap<T> extends QueryStreamBase<T> {
   s: QueryStreamBase<T>;
-  flatMapFn;
+  flatMapFn: (v: T) => T[];
 
-  constructor(s: QueryStreamBase<T>, flatMapFn) {
+  constructor(s: QueryStreamBase<T>, flatMapFn: (v: T) => T[]) {
     super(s.root);
     this.s = s;
-    this.flatMap = flatMapFn;
+    this.flatMapFn = flatMapFn;
   }
 
-  subscribe(f) {
+  subscribe(f: SubscribeFn) {
     this.s.subscribe((isDone, value, tx) => {
       if (isDone) {
         f(true, undefined, tx);
@@ -154,13 +161,13 @@ class QueryStreamFlatMap<T> extends QueryStreamBase<T> {
 }
 
 
-class QueryStreamIndexJoin<T> extends QueryStreamBase<T> {
+class QueryStreamIndexJoin<T,S> extends QueryStreamBase<[T, S]> {
   s: QueryStreamBase<T>;
-  storeName;
-  key;
-  indexName;
+  storeName: string;
+  key: any;
+  indexName: string;
 
-  constructor(s, storeName: string, indexName: string, key: any) {
+  constructor(s: QueryStreamBase<T>, storeName: string, indexName: string, key: any) {
     super(s.root);
     this.s = s;
     this.storeName = storeName;
@@ -168,7 +175,7 @@ class QueryStreamIndexJoin<T> extends QueryStreamBase<T> {
     this.indexName = indexName;
   }
 
-  subscribe(f) {
+  subscribe(f: SubscribeFn) {
     this.s.subscribe((isDone, value, tx) => {
       if (isDone) {
         f(true, undefined, tx);
@@ -192,31 +199,31 @@ class QueryStreamIndexJoin<T> extends QueryStreamBase<T> {
 
 
 class IterQueryStream<T> extends QueryStreamBase<T> {
-  private storeName;
-  private options;
-  private subscribers;
+  private storeName: string;
+  private options: any;
+  private subscribers: SubscribeFn[];
 
-  constructor(qr, storeName, options) {
+  constructor(qr: QueryRoot, storeName: string, options: any) {
     super(qr);
     this.options = options;
     this.storeName = storeName;
     this.subscribers = [];
 
-    let doIt = (tx) => {
+    let doIt = (tx: IDBTransaction) => {
       const {indexName = void 0, only = void 0} = this.options;
-      let s;
+      let s: any;
       if (indexName !== void 0) {
         s = tx.objectStore(this.storeName)
               .index(this.options.indexName);
       } else {
         s = tx.objectStore(this.storeName);
       }
-      let kr = undefined;
-      if (only !== void 0) {
+      let kr: IDBKeyRange|undefined = undefined;
+      if (only !== undefined) {
         kr = IDBKeyRange.only(this.options.only);
       }
       let req = s.openCursor(kr);
-      req.onsuccess = (e) => {
+      req.onsuccess = () => {
         let cursor: IDBCursorWithValue = req.result;
         if (cursor) {
           for (let f of this.subscribers) {
@@ -231,32 +238,33 @@ class IterQueryStream<T> extends QueryStreamBase<T> {
       }
     };
 
-    this.root.addWork(doIt, null, false);
+    this.root.addWork(doIt);
   }
 
-  subscribe(f) {
+  subscribe(f: SubscribeFn) {
     this.subscribers.push(f);
   }
 }
 
 
 class QueryRoot {
-  private work = [];
+  private work: ((t: IDBTransaction) => void)[] = [];
   private db: IDBDatabase;
   private stores = new Set();
-  private kickoffPromise;
+  private kickoffPromise: Promise<void>;
 
   /**
    * Some operations is a write operation,
    * and we need to do a "readwrite" transaction/
    */
-  private hasWrite;
+  private hasWrite: boolean;
 
-  constructor(db) {
+  constructor(db: IDBDatabase) {
     this.db = db;
   }
 
-  iter<T>(storeName, {only = void 0, indexName = void 0} = {}): QueryStream<T> {
+  iter<T>(storeName: string,
+    {only = <string|undefined>undefined, indexName = <string|undefined>undefined} = {}): QueryStream<T> {
     this.stores.add(storeName);
     return new IterQueryStream(this, storeName, {only, indexName});
   }
@@ -266,7 +274,7 @@ class QueryRoot {
    * Overrides if an existing object with the same key exists
    * in the store.
    */
-  put(storeName, val): QueryRoot {
+  put(storeName: string, val: any): QueryRoot {
     let doPut = (tx: IDBTransaction) => {
       tx.objectStore(storeName).put(val);
     };
@@ -280,7 +288,7 @@ class QueryRoot {
    * Fails if the object's key is already present
    * in the object store.
    */
-  putAll(storeName, iterable): QueryRoot {
+  putAll(storeName: string, iterable: any[]): QueryRoot {
     const doPutAll = (tx: IDBTransaction) => {
       for (const obj of iterable) {
         tx.objectStore(storeName).put(obj);
@@ -295,7 +303,7 @@ class QueryRoot {
    * Fails if the object's key is already present
    * in the object store.
    */
-  add(storeName, val): QueryRoot {
+  add(storeName: string, val: any): QueryRoot {
     const doAdd = (tx: IDBTransaction) => {
       tx.objectStore(storeName).add(val);
     };
@@ -306,16 +314,16 @@ class QueryRoot {
   /**
    * Get one object from a store by its key.
    */
-  get(storeName, key): Promise<any> {
+  get(storeName: any, key: any): Promise<any> {
     if (key === void 0) {
       throw Error("key must not be undefined");
     }
 
     const {resolve, promise} = openPromise();
 
-    const doGet = (tx) => {
+    const doGet = (tx: IDBTransaction) => {
       const req = tx.objectStore(storeName).get(key);
-      req.onsuccess = (r) => {
+      req.onsuccess = () => {
         resolve(req.result);
       };
     };
@@ -329,16 +337,16 @@ class QueryRoot {
   /**
    * Get one object from a store by its key.
    */
-  getIndexed(storeName, indexName, key): Promise<any> {
+  getIndexed(storeName: string, indexName: string, key: any): Promise<any> {
     if (key === void 0) {
       throw Error("key must not be undefined");
     }
 
     const {resolve, promise} = openPromise();
 
-    const doGetIndexed = (tx) => {
+    const doGetIndexed = (tx: IDBTransaction) => {
       const req = tx.objectStore(storeName).index(indexName).get(key);
-      req.onsuccess = (r) => {
+      req.onsuccess = () => {
         resolve(req.result);
       };
     };
@@ -356,7 +364,7 @@ class QueryRoot {
     if (this.kickoffPromise) {
       return this.kickoffPromise;
     }
-    this.kickoffPromise = new Promise((resolve, reject) => {
+    this.kickoffPromise = new Promise<void>((resolve, reject) => {
       if (this.work.length == 0) {
         resolve();
         return;
@@ -376,8 +384,8 @@ class QueryRoot {
   /**
    * Delete an object by from the given object store.
    */
-  delete(storeName: string, key): QueryRoot {
-    const doDelete = (tx) => {
+  delete(storeName: string, key: any): QueryRoot {
+    const doDelete = (tx: IDBTransaction) => {
       tx.objectStore(storeName).delete(key);
     };
     this.addWork(doDelete, storeName, true);
@@ -387,17 +395,21 @@ class QueryRoot {
   /**
    * Low-level function to add a task to the internal work queue.
    */
-  addWork(workFn: (IDBTransaction) => void,
-          storeName: string,
-          isWrite: boolean) {
+  addWork(workFn: (t: IDBTransaction) => void,
+          storeName?: string,
+          isWrite?: boolean) {
+    this.work.push(workFn);
+    if (storeName) {
+      this.addStoreAccess(storeName, isWrite);
+    }
+  }
+
+  addStoreAccess(storeName: string, isWrite?: boolean) {
     if (storeName) {
       this.stores.add(storeName);
     }
     if (isWrite) {
       this.hasWrite = true;
     }
-    if (workFn) {
-      this.work.push(workFn);
-    }
   }
 }
\ No newline at end of file
diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts
index 7cb30c358..209c7a253 100644
--- a/lib/wallet/wallet.ts
+++ b/lib/wallet/wallet.ts
@@ -154,12 +154,12 @@ interface Transaction {
 export interface Badge {
   setText(s: string): void;
   setColor(c: string): void;
-  startBusy();
-  stopBusy();
+  startBusy(): void;
+  stopBusy(): void;
 }
 
 
-function deepEquals(x, y) {
+function deepEquals(x: any, y: any): boolean {
   if (x === y) {
     return true;
   }
@@ -179,7 +179,7 @@ function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] {
 }
 
 
-function getTalerStampSec(stamp: string): number {
+function getTalerStampSec(stamp: string): number|null {
   const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/);
   if (!m) {
     return null;
@@ -188,7 +188,7 @@ function getTalerStampSec(stamp: string): number {
 }
 
 
-function setTimeout(f, t) {
+function setTimeout(f: any, t: number) {
   return chrome.extension.getBackgroundPage().setTimeout(f, t);
 }
 
@@ -211,13 +211,13 @@ interface HttpRequestLibrary {
 
   get(url: string|uri.URI): Promise<HttpResponse>;
 
-  postJson(url: string|uri.URI, body): Promise<HttpResponse>;
+  postJson(url: string|uri.URI, body: any): Promise<HttpResponse>;
 
-  postForm(url: string|uri.URI, form): Promise<HttpResponse>;
+  postForm(url: string|uri.URI, form: any): Promise<HttpResponse>;
 }
 
 
-function copy(o) {
+function copy(o: any) {
   return JSON.parse(JSON.stringify(o));
 }
 
@@ -240,7 +240,7 @@ interface KeyUpdateInfo {
 function getWithdrawDenomList(amountAvailable: AmountJson,
                               denoms: Denomination[]): Denomination[] {
   let remaining = Amounts.copy(amountAvailable);
-  let ds: Denomination[] = [];
+  const ds: Denomination[] = [];
 
   console.log("available denoms");
   console.log(denoms);
@@ -362,7 +362,7 @@ export class Wallet {
 
     let x: number;
 
-    function storeExchangeCoin(mc, url) {
+    function storeExchangeCoin(mc: any, url: string) {
       let exchange: IExchangeInfo = mc[0];
       console.log("got coin for exchange", url);
       let coin: Coin = mc[1];
@@ -471,7 +471,7 @@ export class Wallet {
   private recordConfirmPay(offer: Offer,
                            payCoinInfo: PayCoinInfo,
                            chosenExchange: string): Promise<void> {
-    let payReq = {};
+    let payReq: any = {};
     payReq["amount"] = offer.contract.amount;
     payReq["coins"] = payCoinInfo.map((x) => x.sig);
     payReq["H_contract"] = offer.H_contract;
@@ -588,7 +588,7 @@ export class Wallet {
    * Retrieve all necessary information for looking up the contract
    * with the given hash.
    */
-  executePayment(H_contract): Promise<any> {
+  executePayment(H_contract: string): Promise<any> {
     return Promise.resolve().then(() => {
       return Query(this.db)
         .get("transactions", H_contract)
@@ -614,7 +614,7 @@ export class Wallet {
    * First fetch information requred to withdraw from the reserve,
    * then deplete the reserve, withdrawing coins until it is empty.
    */
-  private processReserve(reserveRecord): void {
+  private processReserve(reserveRecord: any): void {
     let retryDelayMs = 100;
     const opId = "reserve-" + reserveRecord.reserve_pub;
     this.startOperation(opId);
@@ -644,7 +644,7 @@ export class Wallet {
   }
 
 
-  private processPreCoin(preCoin, retryDelayMs = 100): void {
+  private processPreCoin(preCoin: any, retryDelayMs = 100): void {
     this.withdrawExecute(preCoin)
         .then((c) => this.storeCoin(c))
         .catch((e) => {
@@ -810,7 +810,7 @@ export class Wallet {
   /**
    * Withdraw coins from a reserve until it is empty.
    */
-  private depleteReserve(reserve, exchange: IExchangeInfo): Promise<void> {
+  private depleteReserve(reserve: any, exchange: IExchangeInfo): Promise<void> {
     let denomsAvailable: Denomination[] = copy(exchange.active_denoms);
     let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount,
                                                  denomsAvailable);
@@ -919,7 +919,7 @@ export class Wallet {
    * Optionally link the reserve entry to the new or existing
    * exchange entry in then DB.
    */
-  updateExchangeFromUrl(baseUrl): Promise<IExchangeInfo> {
+  updateExchangeFromUrl(baseUrl: string): Promise<IExchangeInfo> {
     baseUrl = canonicalizeBaseUrl(baseUrl);
     let reqUrl = URI("keys").absoluteTo(baseUrl);
     return this.http.get(reqUrl).then((resp) => {
@@ -934,8 +934,8 @@ export class Wallet {
 
   private updateExchangeFromJson(baseUrl: string,
                                  exchangeKeysJson: KeysJson): Promise<IExchangeInfo> {
-    let updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date);
-    if (!updateTimeSec) {
+    const updateTimeSec = getTalerStampSec(exchangeKeysJson.list_issue_date);
+    if (updateTimeSec === null) {
       throw Error("invalid update time");
     }
 
@@ -973,9 +973,9 @@ export class Wallet {
                            {indexName: "exchangeBaseUrl", only: baseUrl})
                      .reduce((coin: Coin, suspendedCoins: Coin[]) => {
                        if (!updatedExchangeInfo.active_denoms.find((c) => c.denom_pub == coin.denomPub)) {
-                         return [].concat(suspendedCoins, [coin]);
+                         return Array.prototype.concat(suspendedCoins, [coin]);
                        }
-                       return [].concat(suspendedCoins);
+                       return Array.prototype.concat(suspendedCoins);
                      }, [])
                      .then((suspendedCoins: Coin[]) => {
                        let q = Query(this.db);
@@ -1006,8 +1006,8 @@ export class Wallet {
       let found = false;
       for (let oldDenom of exchangeInfo.all_denoms) {
         if (oldDenom.denom_pub === newDenom.denom_pub) {
-          let a = Object.assign({}, oldDenom);
-          let b = Object.assign({}, newDenom);
+          let a: any = Object.assign({}, oldDenom);
+          let b: any = Object.assign({}, newDenom);
           // pub hash is only there for convenience in the wallet
           delete a["pub_hash"];
           delete b["pub_hash"];
@@ -1055,7 +1055,7 @@ export class Wallet {
    * that is currenctly available for spending in the wallet.
    */
   getBalances(): Promise<any> {
-    function collectBalances(c: Coin, byCurrency) {
+    function collectBalances(c: Coin, byCurrency: any) {
       if (c.suspended) {
         return byCurrency;
       }
@@ -1081,7 +1081,7 @@ export class Wallet {
    * Retrive the full event history for this wallet.
    */
   getHistory(): Promise<any> {
-    function collect(x, acc) {
+    function collect(x: any, acc: any) {
       acc.push(x);
       return acc;
     }
@@ -1106,7 +1106,7 @@ export class Wallet {
                   [contract.merchant_pub, contract.repurchase_correlation_id])
       .then((result: Transaction) => {
         console.log("db result", result);
-        let isRepurchase;
+        let isRepurchase: boolean;
         if (result) {
           console.assert(result.contract.repurchase_correlation_id == contract.repurchase_correlation_id);
           return {
diff --git a/lib/wallet/wxMessaging.ts b/lib/wallet/wxMessaging.ts
index 3ab56af71..401fefd56 100644
--- a/lib/wallet/wxMessaging.ts
+++ b/lib/wallet/wxMessaging.ts
@@ -54,8 +54,12 @@ function makeHandlers(db: IDBDatabase,
       return exportDb(db);
     },
     ["ping"]: function(detail, sender) {
-      let info = paymentRequestCookies[sender.tab.id];
-      delete paymentRequestCookies[sender.tab.id];
+      if (!sender || !sender.tab || !sender.tab.id) {
+        return Promise.resolve();
+      }
+      let id: number = sender.tab.id;
+      let info: any = <any>paymentRequestCookies[id];
+      delete paymentRequestCookies[id];
       return Promise.resolve(info);
     },
     ["reset"]: function(detail, sender) {
@@ -89,7 +93,7 @@ function makeHandlers(db: IDBDatabase,
       return wallet.confirmReserve(req);
     },
     ["confirm-pay"]: function(detail, sender) {
-      let offer;
+      let offer: Offer;
       try {
         offer = Offer.checked(detail.offer);
       } catch (e) {
@@ -108,7 +112,7 @@ function makeHandlers(db: IDBDatabase,
       return wallet.confirmPay(offer);
     },
     ["check-pay"]: function(detail, sender) {
-      let offer;
+      let offer: Offer;
       try {
         offer = Offer.checked(detail.offer);
       } catch (e) {
@@ -181,14 +185,14 @@ class ChromeBadge implements Badge {
 }
 
 
-function dispatch(handlers, req, sender, sendResponse) {
+function dispatch(handlers: any, req: any, sender: any, sendResponse: any) {
   if (req.type in handlers) {
     Promise
       .resolve()
       .then(() => {
         const p = handlers[req.type](req.detail, sender);
 
-        return p.then((r) => {
+        return p.then((r: any) => {
           sendResponse(r);
         })
       })
@@ -242,13 +246,15 @@ class ChromeNotifier implements Notifier {
 /**
  * Mapping from tab ID to payment information (if any).
  */
-let paymentRequestCookies = {};
+let paymentRequestCookies: {[n: number]: any} = {};
 
 function handleHttpPayment(headerList: chrome.webRequest.HttpHeader[],
                            url: string, tabId: number): any {
-  const headers = {};
+  const headers: {[s: string]: string} = {};
   for (let kv of headerList) {
-    headers[kv.name.toLowerCase()] = kv.value;
+    if (kv.value) {
+      headers[kv.name.toLowerCase()] = kv.value;
+    }
   }
 
   const contractUrl = headers["x-taler-contract-url"];
@@ -288,7 +294,7 @@ export function wxMain() {
 
   chrome.tabs.query({}, function(tabs) {
     for (let tab of tabs) {
-      if (!tab.url) {
+      if (!tab.url || !tab.id) {
         return;
       }
       let uri = URI(tab.url);
@@ -338,7 +344,7 @@ export function wxMain() {
                return;
              }
              console.log(`got 402 from ${details.url}`);
-             return handleHttpPayment(details.responseHeaders,
+             return handleHttpPayment(details.responseHeaders || [],
                                       details.url,
                                       details.tabId);
            }, {urls: ["<all_urls>"]}, ["responseHeaders", "blocking"]);
diff --git a/tsconfig.json b/tsconfig.json
index 44814dec5..2df3b2ce8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,7 +7,9 @@
     "sourceMap": true,
     "noLib": true,
     "noImplicitReturns": true,
-    "noFallthroughCasesInSwitch": true
+    "noFallthroughCasesInSwitch": true,
+    "strictNullChecks": true,
+    "noImplicitAny": true
   },
   "files": [
     "lib/i18n.ts",