Notable methods on the Communication class:
-- static generatePKCE(length)
+- `static async generatePKCE(length)`
Create a code and verifier for use in an IndieAuth transaction.
-- fetchProfile(urlObject)
+- `async fetchProfile(urlObject)`
Retrieve profile information from an endpoint.
-- fetchClientIdentifier(urlObject)
+- `async fetchClientIdentifier(urlObject)`
Retrieve client identifier information from an endpoint.
-- fetchMicroformat(urlObject)
- Retrieve all mf2 information form an endpoint.
+- `async fetchMicroformat(urlObject)`
+ Retrieve all mf2 information from an endpoint.
+
+- `async fetchJSON(urlObject)`
+ Retrieve json from an endpoint.
}
+ /**
+ * Retrieve and parse JSON.
+ * N.B. this absorbs any errors!
+ * @param {URL} urlObj
+ * @returns {Object}
+ */
+ async fetchJSON(urlObj) {
+ const _scope = _fileScope('fetchJSON');
+ const logInfoData = {
+ url: urlObj.href,
+ response: undefined,
+ };
+ let response;
+ try {
+ const fetchJSONConfig = Communication._axiosConfig('GET', urlObj, undefined, undefined, {
+ [Enum.Header.Accept]: [Enum.ContentType.ApplicationJson, Enum.ContentType.Any + ';q=0.1'].join(', '),
+ });
+ response = await this.axios(fetchJSONConfig);
+ } catch (e) {
+ this.logger.error(_scope, 'json request failed', { error: e, ...logInfoData });
+ return;
+ }
+ logInfoData.response = common.axiosResponseLogData(response);
+
+ let data;
+ try {
+ data = JSON.parse(response.data);
+ } catch (e) {
+ this.logger.error(_scope, 'json parsing failed', { error: e, ...logInfoData });
+ }
+
+ return data;
+ }
+
+
/**
* @typedef {Object} ClientIdentifierData
* @property {Object} rels - keyed by relation to array of uris
* @property {String} photo
* @property {String} url
* @property {String} email
- * @property {String} authorizationEndpoint
- * @property {String} tokenEndpoint
+ * @property {String} authorizationEndpoint - deprecated, backwards compatibility for 20201126 spec
+ * @property {String} tokenEndpoint - deprecated, backwards compatibility for 20201126 spec
+ * @property {String} indieauthMetadata authorization server metadata endpoint
+ * @property {Object} metadata - authorization server metadata for profile
+ * @property {String} metadata.issuer
+ * @property {String} metadata.authorizationEndpoint
+ * @property {String} metadata.tokenEndpoint
+ * @property {String} metadata.introspectionEndpoint
+ * @property {String} metadata.introspectionEndpointAuthMethodsSupported
+ * @property {String} metadata.revocationEndpoint
+ * @property {String} metadata.revocationEndpointAuthMethodsSupported
+ * @property {String} metadata.scopesSupported
+ * @property {String} metadata.responseTypesSupported
+ * @property {String} metadata.grantTypesSupported
+ * @property {String} metadata.serviceDocumentation
+ * @property {String} metadata.codeChallengeMethodsSupported
+ * @property {String} metadata.authorizationResponseIssParameterSupported
+ * @property {String} metadata.userinfoEndpoint
*/
/**
- * Fetch all the microformat data from url, but only return the relevant h-card profile information.
+ * Fetch the relevant microformat data from profile url h-card information,
+ * and authorization server metadata.
* @param {URL} urlObj
* @returns {ProfileData} mf2 data filtered for select fields from h-card
*/
async fetchProfile(urlObj) {
+ const _scope = _fileScope('fetchProfile');
+
const mfData = await this.fetchMicroformat(urlObj);
const profile = {
name: undefined,
photo: undefined,
url: undefined,
email: undefined,
+ metadata: {},
};
+ // Locate h-card mf2 items with url field matching profile url,
+ // and populate profile fields with first-encountered card values.
if (mfData && 'items' in mfData) {
const hCards = mfData.items.filter((item) =>
item.type && item.type.includes('h-card') &&
});
});
}
+
+ // Populate legacy mf2 fields from relation links.
+ // These will be overwritten if they also exist in server metadata.
Object.entries({
- authorizationEndpoint: 'authorization_endpoint',
- tokenEndpoint: 'token_endpoint',
+ authorizationEndpoint: 'authorization_endpoint', // backwards compatibility
+ tokenEndpoint: 'token_endpoint', // backwards compatibility
}).forEach(([p, r]) => {
if (mfData && r in mfData.rels) {
- profile[p] = mfData.rels[r][0]; // eslint-disable-line security/detect-object-injection
+ profile.metadata[p] = profile[p] = mfData.rels[r][0]; // eslint-disable-line security/detect-object-injection
}
});
+ // Set metadata field.
+ if (mfData && 'indieauth-metadata' in mfData.rels) {
+ profile.indieauthMetadata = mfData.rels['indieauth-metadata'][0];
+ }
+
+ // Attempt to populate metadata from authorization server.
+ if (profile.indieauthMetadata) {
+ let mdURL;
+ try {
+ mdURL = new URL(profile.indieauthMetadata);
+ } catch (e) /* istanbul ignore next */ {
+ this.logger.error(_scope, 'invalid authorization server metadata url', { profile });
+ }
+ /* istanbul ignore else */
+ if (mdURL) {
+ const metadataResponse = await this.fetchJSON(mdURL);
+ if (metadataResponse) {
+ // Map snake_case fields to camelCase.
+ Object.entries({
+ issuer: 'issuer',
+ authorizationEndpoint: 'authorization_endpoint',
+ tokenEndpoint: 'token_endpoint',
+ introspectionEndpoint: 'introspection_endpoint',
+ introspectionEndpointAuthMethodsSupported: 'introspection_endpoint_auth_methods_supported',
+ revocationEndpoint: 'revocation_endpoint',
+ revocationEndpointAuthMethodsSupported: 'revocation_endpoint_auth_methods_supported',
+ scopesSupported: 'scopes_supported',
+ responseTypesSupported: 'response_types_supported',
+ grantTypesSupported: 'grant_types_supported',
+ serviceDocumentation: 'service_documentation',
+ codeChallengeMethodsSupported: 'code_challenge_methods_supported',
+ authorizationResponseIssParameterSupported: 'authorization_response_iss_parameter_supported',
+ userinfoEndpoint: 'userinfo_endpoint',
+ }).forEach(([c, s]) => {
+ if (s in metadataResponse) {
+ profile.metadata[c] = metadataResponse[s]; // eslint-disable-line security/detect-object-injection
+ }
+ });
+
+ // Populate legacy profile fields.
+ ['authorizationEndpoint', 'tokenEndpoint'].forEach((f) => {
+ if (f in profile.metadata) {
+ profile[f] = profile.metadata[f]; // eslint-disable-line security/detect-object-injection
+ }
+ });
+ }
+ }
+ }
+
return profile;
}
'use strict';
module.exports = {
- Specification: 'living-standard-20201126',
+ Specification: 'living-standard-20220212',
ContentType: {
Any: '*/*',
}
},
"@eslint/eslintrc": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz",
- "integrity": "sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz",
+ "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.2.0",
+ "espree": "^9.3.1",
"globals": "^13.9.0",
"ignore": "^4.0.6",
"import-fresh": "^3.2.1",
}
},
"@humanwhocodes/config-array": {
- "version": "0.9.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz",
- "integrity": "sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA==",
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz",
+ "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==",
"dev": true,
"requires": {
"@humanwhocodes/object-schema": "^1.2.1",
}
},
"@sinonjs/fake-timers": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz",
- "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.0.0.tgz",
+ "integrity": "sha512-+shXA2X7KNP7H7qNbQTJ3SA+NQc0pZDSBrdvFSRwF8sAo/ohw+ZQFD8Moc+gnz51+1eRXtEQBpKWPiQ4jsRC/w==",
"dev": true,
"requires": {
"@sinonjs/commons": "^1.7.0"
}
},
"@sinonjs/samsam": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz",
- "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==",
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz",
+ "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==",
"dev": true,
"requires": {
"@sinonjs/commons": "^1.6.0",
}
},
"axios": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
- "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz",
+ "integrity": "sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==",
"requires": {
- "follow-redirects": "^1.14.7"
+ "follow-redirects": "^1.14.8"
}
},
"balanced-match": {
}
},
"chokidar": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
- "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"requires": {
"anymatch": "~3.1.2",
"dev": true
},
"eslint": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.7.0.tgz",
- "integrity": "sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w==",
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz",
+ "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==",
"dev": true,
"requires": {
- "@eslint/eslintrc": "^1.0.5",
+ "@eslint/eslintrc": "^1.1.0",
"@humanwhocodes/config-array": "^0.9.2",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.1.0",
+ "eslint-scope": "^7.1.1",
"eslint-utils": "^3.0.0",
- "eslint-visitor-keys": "^3.2.0",
- "espree": "^9.3.0",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.3.1",
"esquery": "^1.4.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
}
},
"eslint-plugin-sonarjs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.11.0.tgz",
- "integrity": "sha512-ei/WuZiL0wP+qx2KrxKyZs3+eDbxiGAhFSm3GKCOOAUkg+G2ny6TSXDB2j67tvyqHefi+eoQsAgGQvz+nEtIBw==",
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.12.0.tgz",
+ "integrity": "sha512-soxjK67hoYxO8hesKqXWN50GSM+oG2r35N5WnAMehetahO6zoVpv3HZbdziP0jYWNopEe6te/BFUZOYAZI+qhg==",
"dev": true
},
"eslint-scope": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz",
- "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
"dev": true,
"requires": {
"esrecurse": "^4.3.0",
}
},
"eslint-visitor-keys": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz",
- "integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
"dev": true
},
"espree": {
- "version": "9.3.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz",
- "integrity": "sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==",
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz",
+ "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==",
"dev": true,
"requires": {
"acorn": "^8.7.0",
"acorn-jsx": "^5.3.1",
- "eslint-visitor-keys": "^3.1.0"
+ "eslint-visitor-keys": "^3.3.0"
}
},
"esprima": {
}
},
"flatted": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz",
- "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==",
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz",
+ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true
},
"follow-redirects": {
- "version": "1.14.7",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
- "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ=="
+ "version": "1.14.8",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
+ "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA=="
},
"foreground-child": {
"version": "2.0.0",
}
},
"globals": {
- "version": "13.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
- "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
+ "version": "13.12.1",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz",
+ "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==",
"dev": true,
"requires": {
"type-fest": "^0.20.2"
"dev": true
},
"mocha": {
- "version": "9.1.4",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.4.tgz",
- "integrity": "sha512-+q2aV5VlJZuLgCWoBvGI5zEwPF9eEI0kr/sAA9Jm4xMND7RfIEyF8JE7C0JIg8WXRG+P1sdIAb5ccoHPlXLzcw==",
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.0.tgz",
+ "integrity": "sha512-kNn7E8g2SzVcq0a77dkphPsDSN7P+iYkqE0ZsGCYWRsoiKjOt+NvXfaagik8vuDa6W5Zw3qxe8Jfpt5qKf+6/Q==",
"dev": true,
"requires": {
"@ungap/promise-all-settled": "1.1.2",
"ansi-colors": "4.1.1",
"browser-stdout": "1.3.1",
- "chokidar": "3.5.2",
- "debug": "4.3.2",
+ "chokidar": "3.5.3",
+ "debug": "4.3.3",
"diff": "5.0.0",
"escape-string-regexp": "4.0.0",
"find-up": "5.0.0",
- "glob": "7.1.7",
+ "glob": "7.2.0",
"growl": "1.10.5",
"he": "1.2.0",
"js-yaml": "4.1.0",
"log-symbols": "4.1.0",
"minimatch": "3.0.4",
"ms": "2.1.3",
- "nanoid": "3.1.25",
+ "nanoid": "3.2.0",
"serialize-javascript": "6.0.0",
"strip-json-comments": "3.1.1",
"supports-color": "8.1.1",
"which": "2.0.2",
- "workerpool": "6.1.5",
+ "workerpool": "6.2.0",
"yargs": "16.2.0",
"yargs-parser": "20.2.4",
"yargs-unparser": "2.0.0"
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
- "glob": {
- "version": "7.1.7",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
- "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+ "debug": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
"dev": true,
"requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
+ "ms": "2.1.2"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
}
},
"has-flag": {
"dev": true
},
"nanoid": {
- "version": "3.1.25",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz",
- "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
+ "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==",
"dev": true
},
"natural-compare": {
"dev": true
},
"nise": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz",
- "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz",
+ "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==",
"dev": true,
"requires": {
- "@sinonjs/commons": "^1.7.0",
- "@sinonjs/fake-timers": "^7.0.4",
+ "@sinonjs/commons": "^1.8.3",
+ "@sinonjs/fake-timers": ">=5",
"@sinonjs/text-encoding": "^0.7.1",
"just-extend": "^4.0.2",
"path-to-regexp": "^1.7.0"
- },
- "dependencies": {
- "@sinonjs/fake-timers": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz",
- "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==",
- "dev": true,
- "requires": {
- "@sinonjs/commons": "^1.7.0"
- }
- }
}
},
"node-preload": {
"dev": true
},
"sinon": {
- "version": "12.0.1",
- "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz",
- "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==",
+ "version": "13.0.1",
+ "resolved": "https://registry.npmjs.org/sinon/-/sinon-13.0.1.tgz",
+ "integrity": "sha512-8yx2wIvkBjIq/MGY1D9h1LMraYW+z1X0mb648KZnKSdvLasvDu7maa0dFaNYdTDczFgbjNw2tOmWdTk9saVfwQ==",
"dev": true,
"requires": {
"@sinonjs/commons": "^1.8.3",
- "@sinonjs/fake-timers": "^8.1.0",
- "@sinonjs/samsam": "^6.0.2",
+ "@sinonjs/fake-timers": "^9.0.0",
+ "@sinonjs/samsam": "^6.1.1",
"diff": "^5.0.0",
- "nise": "^5.1.0",
+ "nise": "^5.1.1",
"supports-color": "^7.2.0"
},
"dependencies": {
"dev": true
},
"workerpool": {
- "version": "6.1.5",
- "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz",
- "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==",
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz",
+ "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==",
"dev": true
},
"wrap-ansi": {
"license": "ISC",
"dependencies": {
"@squeep/web-linking": "git+https://git.squeep.com/squeep-web-linking/#v1.0.3",
- "axios": "^0.25.0",
+ "axios": "^0.26.0",
"iconv": "^3.0.1",
"microformats-parser": "^1.4.1"
},
"devDependencies": {
- "eslint": "^8.7.0",
+ "eslint": "^8.9.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-security": "^1.4.0",
- "eslint-plugin-sonarjs": "^0.11.0",
- "mocha": "^9.1.4",
+ "eslint-plugin-sonarjs": "^0.12.0",
+ "mocha": "^9.2.0",
"nyc": "^15.1.0",
"pre-commit": "^1.2.2",
- "sinon": "^12.0.1"
+ "sinon": "^13.0.1"
}
}
});
}); // fetchMicroformat
+ describe('fetchJSON', function () {
+ let expected, response, result, urlObj;
+ beforeEach(function () {
+ expected = undefined;
+ result = undefined;
+ urlObj = new URL('https://thuza.ratfeathers.com/');
+ response = {
+ headers: Object.assign({}, testData.linkHeaders),
+ data: testData.hCardHtml,
+ };
+ });
+ it('covers', async function () {
+ communication.axios.resolves(response);
+ expected = { foo: 'bar', baz: 123 };
+ response.data = JSON.stringify(expected);
+
+ result = await communication.fetchJSON(urlObj);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('covers axios error', async function () {
+ communication.axios.rejects(new Error('blah'));
+ expected = undefined;
+
+ result = await communication.fetchJSON(urlObj);
+
+ assert.deepStrictEqual(result, expected);
+ });
+ it('covers non-parsable content', async function () {
+ response.data = 'some bare text';
+ response.headers = {};
+ communication.axios.resolves(response);
+ expected = undefined;
+
+ result = await communication.fetchJSON(urlObj);
+
+ assert.deepStrictEqual(result, expected);
+ });
+ }); // fetchJSON
+
describe('fetchClientIdentifier', function () {
let expected, response, result, urlObj;
beforeEach(function () {
headers: {},
data: testData.hCardHtml,
};
+ sinon.stub(communication, 'fetchJSON');
+ });
+ describe('legacy without indieauth-metadata', function () {
+ it('covers', async function () {
+ communication.axios.resolves(response);
+ expected = {
+ name: 'Thuza',
+ photo: 'https://thuza.ratfeathers.com/image.png',
+ url: 'https://thuza.ratfeathers.com/',
+ email: undefined,
+ authorizationEndpoint: 'https://ia.squeep.com/auth',
+ tokenEndpoint: 'https://ia.squeep.com/token',
+ metadata: {
+ authorizationEndpoint: 'https://ia.squeep.com/auth',
+ tokenEndpoint: 'https://ia.squeep.com/token',
+ },
+ };
+ result = await communication.fetchProfile(urlObj);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('covers multiple hCards', async function () {
+ response.data = testData.multiMF2Html;
+ communication.axios.resolves(response);
+ expected = {
+ email: undefined,
+ name: 'Thuza',
+ photo: 'https://thuza.ratfeathers.com/image.png',
+ url: 'https://thuza.ratfeathers.com/',
+ authorizationEndpoint: 'https://ia.squeep.com/auth',
+ tokenEndpoint: 'https://ia.squeep.com/token',
+ metadata: {
+ authorizationEndpoint: 'https://ia.squeep.com/auth',
+ tokenEndpoint: 'https://ia.squeep.com/token',
+ },
+ };
+ result = await communication.fetchProfile(urlObj);
+ assert.deepStrictEqual(result, expected);
+ });
+ it('covers failed fetch', async function () {
+ communication.axios.rejects();
+ expected = {
+ email: undefined,
+ name: undefined,
+ photo: undefined,
+ url: undefined,
+ metadata: {},
+ };
+ result = await communication.fetchProfile(urlObj);
+ assert.deepStrictEqual(result, expected);
+ });
});
it('covers', async function () {
+ response.data = testData.hCardMetadataHtml;
communication.axios.resolves(response);
+ communication.fetchJSON.resolves({
+ 'issuer': 'https://ia.squeep.com/',
+ 'authorization_endpoint': 'https://ia.squeep.com/auth',
+ 'token_endpoint': 'https://ia.squeep.com/token',
+ 'introspection_endpoint': 'https://ia.squeep.com/introspect',
+ 'introspection_endpoint_auth_methods_supported': [ '' ],
+ 'revocation_endpoint': 'https://ia.squeep.com/revoke',
+ 'revocation_endpoint_auth_methods_supported': [ 'none' ],
+ 'scopes_supported': [ 'profile', 'email' ],
+ 'service_documentation': 'https://indieauth.spec.indieweb.org/',
+ 'code_challenge_methods_supported': [ 'S256', 'SHA256' ],
+ 'authorization_response_iss_parameter_supported': true,
+ 'userinfo_endpoint': 'https://ia.squeep.com/userinfo',
+ });
expected = {
name: 'Thuza',
photo: 'https://thuza.ratfeathers.com/image.png',
url: 'https://thuza.ratfeathers.com/',
email: undefined,
+ metadata: {
+ authorizationEndpoint: 'https://ia.squeep.com/auth',
+ tokenEndpoint: 'https://ia.squeep.com/token',
+ issuer: 'https://ia.squeep.com/',
+ introspectionEndpoint: 'https://ia.squeep.com/introspect',
+ introspectionEndpointAuthMethodsSupported: [ '' ],
+ revocationEndpoint: 'https://ia.squeep.com/revoke',
+ revocationEndpointAuthMethodsSupported: [ 'none' ],
+ scopesSupported: [ 'profile', 'email' ],
+ serviceDocumentation: 'https://indieauth.spec.indieweb.org/',
+ codeChallengeMethodsSupported: [ 'S256', 'SHA256' ],
+ authorizationResponseIssParameterSupported: true,
+ userinfoEndpoint: 'https://ia.squeep.com/userinfo',
+ },
authorizationEndpoint: 'https://ia.squeep.com/auth',
tokenEndpoint: 'https://ia.squeep.com/token',
+ indieauthMetadata: 'https://ia.squeep.com/meta',
};
+
result = await communication.fetchProfile(urlObj);
+
assert.deepStrictEqual(result, expected);
});
- it('covers multiple hCards', async function () {
- response.data = testData.multiMF2Html;
+ it('covers metadata missing fields', async function () {
+ response.data = testData.hCardMetadataHtml;
communication.axios.resolves(response);
+ communication.fetchJSON.resolves({
+ 'issuer': 'https://ia.squeep.com/',
+ });
expected = {
- email: undefined,
name: 'Thuza',
photo: 'https://thuza.ratfeathers.com/image.png',
url: 'https://thuza.ratfeathers.com/',
- authorizationEndpoint: 'https://ia.squeep.com/auth',
- tokenEndpoint: 'https://ia.squeep.com/token',
+ email: undefined,
+ metadata: {
+ issuer: 'https://ia.squeep.com/',
+ },
+ indieauthMetadata: 'https://ia.squeep.com/meta',
};
+
result = await communication.fetchProfile(urlObj);
+
assert.deepStrictEqual(result, expected);
});
- it('covers failed fetch', async function () {
- communication.axios.rejects();
+ it('covers metadata response failure', async function () {
+ const jsonError = new Error('oh no');
+ response.data = testData.hCardMetadataHtml;
+ communication.axios
+ .onCall(0).resolves(response)
+ .onCall(1).rejects(jsonError);
+ communication.fetchJSON.restore();
expected = {
+ name: 'Thuza',
+ photo: 'https://thuza.ratfeathers.com/image.png',
+ url: 'https://thuza.ratfeathers.com/',
email: undefined,
- name: undefined,
- photo: undefined,
- url: undefined,
+ metadata: {},
+ indieauthMetadata: 'https://ia.squeep.com/meta',
};
+
result = await communication.fetchProfile(urlObj);
+
assert.deepStrictEqual(result, expected);
});
}); // fetchProfile
});
it('covers', async function () {
communication.axios.resolves({
- data: '{"me":"https://profile.example.com/"}'
+ data: '{"me":"https://profile.example.com/"}',
});
expected = {
me: 'https://profile.example.com/',
</section>
</main>
</body>
+</html>`,
+ hCardMetadataHtml: `<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+ <title>Thuza Exists</title>
+ <link rel="indieauth-metadata" href="https://ia.squeep.com/meta">
+ <link rel="canonical" href="https://thuza.ratfeathers.com/">
+ </head>
+ <body>
+ <main>
+ <section>
+ Nothing to share yet, only a profile.
+ </section>
+ <section hidden class="h-card p-author">
+ <img class="u-photo" src="image.png">
+ <a class="p-name u-url" rel="author me" href="">Thuza</a>
+ </section>
+ </main>
+ </body>
</html>`,
};