diff --git a/.prettierrc b/.prettierrc index f01b64b..e3c4b95 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,5 @@ { "jsxBracketSameLine": false, - "trailingComma": "all", "tabWidth": 2, "printWidth": 80, "singleQuote": true, diff --git a/package-lock.json b/package-lock.json index e1aa693..dd08fee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "https-did-resolver", - "version": "1.0.1", + "name": "web-did-resolver", + "version": "1.0.2-dev.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1099,6 +1099,15 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "cross-fetch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz", + "integrity": "sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw==", + "requires": { + "node-fetch": "2.6.0", + "whatwg-fetch": "3.0.0" + } + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -1312,12 +1321,6 @@ } } }, - "dom-walk": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", - "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", - "dev": true - }, "domexception": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", @@ -1670,8 +1673,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -1692,14 +1694,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1714,20 +1714,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -1844,8 +1841,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -1857,7 +1853,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1872,7 +1867,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1880,14 +1874,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -1906,7 +1898,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -1987,8 +1978,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2000,7 +1990,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2086,8 +2075,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2123,7 +2111,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2143,7 +2130,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2187,14 +2173,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -2264,16 +2248,6 @@ "is-glob": "^2.0.0" } }, - "global": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", - "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", - "dev": true, - "requires": { - "min-document": "^2.19.0", - "process": "~0.5.1" - } - }, "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", @@ -3574,15 +3548,6 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "dev": true, - "requires": { - "dom-walk": "^0.1.0" - } - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -3692,6 +3657,11 @@ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4098,12 +4068,6 @@ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, - "process": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", - "dev": true - }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -4144,12 +4108,6 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, "randomatic": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", @@ -5483,24 +5441,6 @@ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -5601,6 +5541,11 @@ "iconv-lite": "0.4.24" } }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, "whatwg-mimetype": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz", @@ -5706,27 +5651,12 @@ "async-limiter": "~1.0.0" } }, - "xhr-mock": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/xhr-mock/-/xhr-mock-2.4.1.tgz", - "integrity": "sha1-y1AuPVC4suwxvWF2bOUWv8HdBy8=", - "dev": true, - "requires": { - "global": "^4.3.0", - "url": "^0.11.0" - } - }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, - "xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" - }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", diff --git a/package.json b/package.json index a3d1d7e..4df4733 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "web-did-resolver", - "version": "1.0.1", + "version": "1.0.2-dev.0", "description": "Resolve DID documents from an https domain", "main": "lib/resolver.js", "types": "lib/resolver.d.ts", "author": "Mike Xu ", "license": "Apache-2.0", "dependencies": { - "did-resolver": "1.0.0", - "xmlhttprequest": "^1.8.0" + "cross-fetch": "^3.0.4", + "did-resolver": "1.0.0" }, "scripts": { "build": "tsc", @@ -16,9 +16,6 @@ "dev": "tsc --watch", "format": "prettier" }, - "browser": { - "xmlhttprequest": false - }, "devDependencies": { "@types/jest": "^23.3.10", "jest": "^23.6.0", @@ -28,8 +25,7 @@ "tslint": "^5.12.0", "tslint-config-prettier": "^1.17.0", "tslint-eslint-rules": "^5.4.0", - "typescript": "^3.2.2", - "xhr-mock": "^2.4.1" + "typescript": "^3.2.2" }, "jest": { "transform": { diff --git a/src/__tests__/resolver.test.ts b/src/__tests__/resolver.test.ts index 21cf8a1..6fa454a 100644 --- a/src/__tests__/resolver.test.ts +++ b/src/__tests__/resolver.test.ts @@ -1,12 +1,13 @@ -import { Resolver, DIDDocument, DIDResolver } from 'did-resolver' +import { Resolver, DIDResolver, DIDDocument } from 'did-resolver' import getResolver from '../resolver' -import mock from 'xhr-mock' +import fetch from 'cross-fetch' +jest.mock('cross-fetch') +const mockedFetch = fetch as jest.Mock describe('web did resolver', () => { const did: string = 'did:web:example.com' - const url: string = 'https://example.com/.well-known/did.json' const identity: string = '0x2Cc31912B2b0f3075A87b3640923D45A26cef3Ee' - const validDidDoc: DIDDocument = { + const validResponse: DIDDocument = { '@context': 'https://w3id.org/did/v1', id: did, publicKey: [ @@ -14,33 +15,32 @@ describe('web did resolver', () => { id: `${did}#owner`, type: 'Secp256k1VerificationKey2018', owner: did, - ethereumAddress: identity, - }, + ethereumAddress: identity + } ], authentication: [ { type: 'Secp256k1SignatureAuthentication2018', - publicKey: `${did}#owner`, - }, - ], + publicKey: `${did}#owner` + } + ] } - const validResponse: string = JSON.stringify(validDidDoc) - const noContextResponse: string = JSON.stringify({ - id: validDidDoc.id, - publicKey: validDidDoc.publicKey, - authentication: validDidDoc.authentication, - }) - const wrongIdResponse: string = JSON.stringify({ - '@context': validDidDoc['@context'], + const noContextResponse: object = { + id: validResponse.id, + publicKey: validResponse.publicKey, + authentication: validResponse.authentication + } + const wrongIdResponse: object = { + '@context': validResponse['@context'], id: 'did:web:wrong.com', - publicKey: validDidDoc.publicKey, - authentication: validDidDoc.authentication, - }) - const noPublicKeyResponse: string = JSON.stringify({ - '@context': validDidDoc['@context'], - id: validDidDoc.id, - authentication: validDidDoc.authentication, - }) + publicKey: validResponse.publicKey, + authentication: validResponse.authentication + } + const noPublicKeyResponse: object = { + '@context': validResponse['@context'], + id: validResponse.id, + authentication: validResponse.authentication + } let didResolver: Resolver let webDidResolver: { [index: string]: DIDResolver } @@ -49,46 +49,54 @@ describe('web did resolver', () => { webDidResolver = getResolver() didResolver = new Resolver(webDidResolver) }) - beforeEach(() => mock.setup()) - afterEach(() => mock.teardown()) it('resolves document', () => { - mock.get(url, { status: 200, body: validResponse }) - return expect(didResolver.resolve(did)).resolves.toEqual(validDidDoc) + mockedFetch.mockResolvedValueOnce({ + json: () => validResponse + }) + return expect(didResolver.resolve(did)).resolves.toEqual(validResponse) }) it('fails if the did is not a valid https url', () => { - mock.get(url, { status: 404 }) - return expect(didResolver.resolve(did)).rejects.toThrowError( - 'DID must resolve to a valid https URL: Invalid http response status 404', - ) + mockedFetch.mockRejectedValueOnce({ status: 404 }) + return expect(didResolver.resolve(did)).rejects.toThrow() }) it('fails if the did document is not valid json', () => { - mock.get(url, { status: 200, body: 'invalid json' }) + mockedFetch.mockResolvedValueOnce({ + json: () => { + throw new Error('unable to parse json') + } + }) return expect(didResolver.resolve(did)).rejects.toThrowError( - 'DID must resolve to a JSON document', + /unable to parse json/ ) }) it('fails if the did document is missing a context', () => { - mock.get(url, { status: 200, body: noContextResponse }) + mockedFetch.mockResolvedValueOnce({ + json: () => noContextResponse + }) return expect(didResolver.resolve(did)).rejects.toThrowError( - 'DID document missing context', + 'DID document missing context' ) }) it('fails if the did document id does not match', () => { - mock.get(url, { status: 200, body: wrongIdResponse }) + mockedFetch.mockResolvedValueOnce({ + json: () => wrongIdResponse + }) return expect(didResolver.resolve(did)).rejects.toThrowError( - 'DID document id does not match requested did', + 'DID document id does not match requested did' ) }) it('fails if the did document has no public keys', () => { - mock.get(url, { status: 200, body: noPublicKeyResponse }) + mockedFetch.mockResolvedValueOnce({ + json: () => noPublicKeyResponse + }) return expect(didResolver.resolve(did)).rejects.toThrowError( - 'DID document has no public keys', + 'DID document has no public keys' ) }) }) diff --git a/src/resolver.ts b/src/resolver.ts index dbb950f..62fad20 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -1,70 +1,45 @@ import { ParsedDID, DIDDocument } from 'did-resolver' - -declare global { - interface Window { - XMLHttpRequest: any - } -} -declare var require: any +import fetch from 'cross-fetch' const DOC_PATH = '/.well-known/did.json' -function get(url: string): Promise { - return new Promise((resolve, reject) => { - // declare XMLHttpRequest in here so it can be mocked for tests - const XMLHttpRequest = - typeof window !== 'undefined' - ? window.XMLHttpRequest - : require('xmlhttprequest').XMLHttpRequest - - const request = new XMLHttpRequest() - request.open('GET', url) - request.onreadystatechange = () => { - if (!request || request.readyState !== 4) return - if (request.status === 200) { - resolve(request.responseText) - } else { - reject( - new Error( - `Invalid http response status ${request.status} ${ - request.responseText - }`.trim(), - ), - ) - } +async function get(url: string): Promise { + const res = await fetch(url, { + headers: { + 'Access-Control-Allow-Origin': '*' } - request.setRequestHeader('accept', 'application/json') - request.send() }) + if (res.status >= 400) { + throw new Error(`Bad response ${res.statusText}`) + } + return res.json() } export default function getResolver() { async function resolve( did: string, - parsed: ParsedDID, + parsed: ParsedDID ): Promise { const url: string = `https://${parsed.id}${DOC_PATH}` - let response: any = null - try { - response = await get(url) - } catch (error) { - throw new Error(`DID must resolve to a valid https URL: ${error.message}`) - } - let data: any = null try { - data = JSON.parse(response) + data = await get(url) } catch (error) { - throw new Error('DID must resolve to a JSON document') + throw new Error( + `DID must resolve to a valid https URL containing a JSON document: ${ + error.message + }` + ) } const hasContext = data['@context'] === 'https://w3id.org/did/v1' if (!hasContext) throw new Error('DID document missing context') const docIdMatchesDid = data.id === did - if (!docIdMatchesDid) + if (!docIdMatchesDid) { throw new Error('DID document id does not match requested did') + } const docHasPublicKey = Array.isArray(data.publicKey) && data.publicKey.length > 0 @@ -73,5 +48,5 @@ export default function getResolver() { return data } - return { 'web': resolve } + return { web: resolve } }