fix: better lookup for profile location, fixes #377

This commit is contained in:
Hank Stoever
2020-06-29 12:01:37 -04:00
parent 3c6688714b
commit f292cc13ae
11 changed files with 154 additions and 14 deletions

View File

@@ -71,7 +71,7 @@ module.exports = {
// ],
// An array of file extensions your modules use
moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'],
moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node', 'd.ts'],
// A map from regular expressions to module names that allow to stub out resources with a single module
moduleNameMapper: {

View File

@@ -77,7 +77,9 @@ module.exports = {
},
output: {
path: distRootPath,
chunkFilename: !isDevelopment ? '[name].[contenthash:8].chunk.js' : isDevelopment && '[name].chunk.js',
chunkFilename: !isDevelopment
? '[name].[contenthash:8].chunk.js'
: isDevelopment && '[name].chunk.js',
filename: () => {
if (extEnv === 'prod' || isDevelopment) {
return '[name].js';
@@ -86,7 +88,7 @@ module.exports = {
},
},
resolve: {
extensions: ['.js', '.ts', '.tsx', '.json'],
extensions: ['.js', '.ts', '.tsx', '.json', '.d.ts'],
plugins: [new TsconfigPathsPlugin()],
alias: aliases,
},
@@ -233,7 +235,9 @@ module.exports = {
STATS_URL: JSON.stringify(statsURL),
}),
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
isDevelopment && extEnv === 'web' && new ReactRefreshWebpackPlugin({ disableRefreshCheck: true }),
isDevelopment &&
extEnv === 'web' &&
new ReactRefreshWebpackPlugin({ disableRefreshCheck: true }),
].filter(Boolean),
};

View File

@@ -10,6 +10,7 @@ module.exports = {
},
},
},
moduleFileExtensions: ['js', 'ts', 'd.ts'],
setupFiles: ['./tests/global-setup.ts'],
setupFilesAfterEnv: ['./tests/setup.ts'],
};

View File

@@ -65,7 +65,8 @@
"c32check": "^1.0.1",
"jsontokens": "^3.0.0",
"prettier": "^2.0.5",
"triplesec": "^3.0.27"
"triplesec": "^3.0.27",
"zone-file": "^1.0.0"
},
"publishConfig": {
"access": "public"

View File

@@ -1,6 +1,7 @@
import { bip32, ECPair } from 'bitcoinjs-lib';
import { getPublicKeyFromPrivate } from 'blockstack/lib/keys';
import { makeAuthResponse } from 'blockstack/lib/auth/authMessages';
import { getProfileURLFromZoneFile } from './utils';
import { IdentityKeyPair } from './utils/index';
import {
@@ -116,9 +117,17 @@ export class Identity {
return appsNode.getAppPrivateKey(appDomain);
}
// eslint-disable-next-line @typescript-eslint/require-await
async profileUrl(gaiaUrl: string) {
// future proofing for code that may require network requests to find profile
async profileUrl(gaiaUrl: string): Promise<string> {
if (this.defaultUsername) {
try {
const url = await getProfileURLFromZoneFile(this.defaultUsername);
if (url) return url;
} catch (error) {
if (process.env.NODE_ENV !== 'test') {
console.warn('Error fetching profile URL from zone file:', error);
}
}
}
return `${gaiaUrl}${this.address}/profile.json`;
}
@@ -135,10 +144,7 @@ export class Identity {
*/
async refresh(opts: RefreshOptions = { gaiaUrl: DEFAULT_GAIA_HUB }) {
try {
const [names, profile] = await Promise.all([
this.fetchNames(),
fetchProfile({ identity: this, gaiaUrl: opts.gaiaUrl }),
]);
const names = await this.fetchNames();
if (names) {
if (names[0] && !this.defaultUsername) {
this.defaultUsername = names[0];
@@ -150,6 +156,7 @@ export class Identity {
}
});
}
const profile = await fetchProfile({ identity: this, gaiaUrl: opts.gaiaUrl });
if (profile) {
this.profile = profile;
}

View File

@@ -0,0 +1,11 @@
declare module 'zone-file' {
interface URI {
target: string;
}
export interface ZoneFile {
$origin: string;
uri: URI[];
}
export const parseZoneFile: (zoneFile: string) => ZoneFile
}

View File

@@ -2,6 +2,8 @@ import { BIP32Interface } from 'bitcoinjs-lib';
import IdentityAddressOwnerNode from '../nodes/identity-address-owner-node';
import { createSha2Hash } from 'blockstack/lib/encryption/sha2Hash';
import { publicKeyToAddress } from 'blockstack/lib/keys';
import '../types/zone-file';
import { parseZoneFile } from 'zone-file';
import Identity from '../identity';
import { AssertionError } from 'assert';
import { Subdomains, registrars } from '../profiles';
@@ -241,3 +243,19 @@ export const validateSubdomain = async (
return null;
};
interface NameInfoResponse {
address: string;
zonefile: string;
}
export const getProfileURLFromZoneFile = async (name: string) => {
const url = `https://core.blockstack.org/v1/names/${name}`;
const res = await fetch(url);
if (res.ok) {
const nameInfo: NameInfoResponse = await res.json();
const zone = parseZoneFile(nameInfo.zonefile);
return zone.uri[0].target;
}
return;
};

View File

@@ -65,3 +65,17 @@ export const profileResponse = [
},
},
];
export const nameInfoResponse = {
address: '1J3PUxY5uDShUnHRrMyU6yKtoHEUPhKULs',
blockchain: 'bitcoin',
expire_block: 599266,
grace_period: false,
last_txid: '1edfa419f7b83f33e00830bc9409210da6c6d1db60f99eda10c835aa339cad6b',
renewal_deadline: 604266,
resolver: null,
status: 'registered',
zonefile:
'$ORIGIN muneeb.id\n$TTL 3600\n_http._tcp IN URI 10 1 "https://gaia.blockstack.org/hub/1J3PUxY5uDShUnHRrMyU6yKtoHEUPhKULs/0/profile.json"\n',
zonefile_hash: '37aecf837c6ae9bdc9dbd98a268f263dacd00361',
};

View File

@@ -2,7 +2,7 @@ import './setup';
import { makeECPrivateKey, getPublicKeyFromPrivate } from 'blockstack/lib/keys';
import { decryptPrivateKey } from 'blockstack/lib/auth/authMessages';
import { decodeToken } from 'jsontokens';
import { getIdentity, profileResponse } from './helpers';
import { getIdentity, profileResponse, nameInfoResponse } from './helpers';
import { ecPairToAddress } from 'blockstack';
import { ECPair } from 'bitcoinjs-lib';
import { getAddress } from '../src';
@@ -41,6 +41,8 @@ test('adds to apps in profile if publish_data scope', async () => {
.once(JSON.stringify({}), { status: 404 })
.once(JSON.stringify({ read_url_prefix: 'https://gaia.blockstack.org/hub/' }))
.once(JSON.stringify({ read_url_prefix: 'https://gaia.blockstack.org/hub/' }))
.once(JSON.stringify({}))
.once(JSON.stringify({}))
.once(JSON.stringify({}));
const identity = await getIdentity();
const appDomain = 'https://banter.pub';
@@ -92,16 +94,25 @@ test('gets default profile URL', async () => {
);
});
test('can get a profile URL from a zone file', async () => {
const identity = await getIdentity();
fetchMock.once(JSON.stringify(nameInfoResponse));
const profileURL = await identity.profileUrl('asdf');
return;
});
describe('refresh', () => {
test('can fetch names for an identity', async () => {
const identity = await getIdentity();
fetchMock.once(JSON.stringify({ names: ['myname.id'] }));
fetchMock.once(JSON.stringify(nameInfoResponse));
fetchMock.once(JSON.stringify(profileResponse));
await identity.refresh();
expect(identity.defaultUsername).toEqual('myname.id');
expect(identity.usernames).toEqual(['myname.id']);
expect(identity.profile).toBeTruthy();
});
test('can fetch multiple usernames', async () => {

View File

@@ -9,7 +9,7 @@ import {
import { Subdomains, registrars, Wallet, decrypt } from '../src';
import { mnemonicToSeed } from 'bip39';
import { bip32 } from 'bitcoinjs-lib';
import { profileResponse } from './helpers';
import { profileResponse, nameInfoResponse } from './helpers';
import { ChainID } from '@blockstack/stacks-transactions';
describe(validateSubdomainFormat.name, () => {
@@ -101,12 +101,16 @@ test('recursively makes identities', async () => {
fetchMock
.once(JSON.stringify({ names: ['myname.id'] }))
.once(JSON.stringify(nameInfoResponse))
.once(JSON.stringify(profileResponse))
.once(JSON.stringify({ names: ['myname2.id'] }))
.once(JSON.stringify(nameInfoResponse))
.once(JSON.stringify(profileResponse))
.once(JSON.stringify({ names: ['myname3.id'] }))
.once(JSON.stringify(nameInfoResponse))
.once(JSON.stringify(profileResponse))
.once(JSON.stringify({ names: [] }))
.once('', { status: 404 })
.once(JSON.stringify(profileResponse));
const identities = await recursiveRestoreIdentities({ rootNode });
expect(identities[0].defaultUsername).toEqual('myname.id');

View File

@@ -1673,6 +1673,27 @@
sha.js "^2.4.11"
smart-buffer "^4.1.0"
"@blockstack/stacks-transactions@^0.4.6":
version "0.4.6"
resolved "https://registry.yarnpkg.com/@blockstack/stacks-transactions/-/stacks-transactions-0.4.6.tgz#b774250fbaadbadf42313a82fbd628b1728cd6ed"
integrity sha512-3Hb+v0ZmG5bVZHasfM9KzlwK+2e5r6oKsKk0eRgavLb5bBMQy/cw0YYoUWmt+ipNqDP5ssZgoOA1KKRhoSWvXg==
dependencies:
"@types/bn.js" "^4.11.6"
"@types/elliptic" "^6.4.12"
"@types/lodash" "^4.14.149"
"@types/randombytes" "^2.0.0"
"@types/ripemd160" "^2.0.0"
"@types/sha.js" "^2.4.0"
bn.js "^4.11.8"
c32check "^1.0.1"
cross-fetch "^3.0.4"
elliptic "^6.5.2"
lodash "^4.17.15"
randombytes "^2.1.0"
ripemd160 "^2.0.2"
sha.js "^2.4.11"
smart-buffer "^4.1.0"
"@blockstack/stats@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@blockstack/stats/-/stats-0.7.0.tgz#be39d3e76c2a16c1cd1283aa702d1e20b5e04645"
@@ -5433,6 +5454,32 @@ blockstack@21.0.0:
uuid "^3.3.3"
zone-file "^1.0.0"
blockstack@21.0.0-alpha.2:
version "21.0.0-alpha.2"
resolved "https://registry.yarnpkg.com/blockstack/-/blockstack-21.0.0-alpha.2.tgz#1f223387df24b5770d5e7ec4031df52b8014ca11"
integrity sha512-I7FQTYU78H/Eok/is0G79dSaQ7tD0DuKGKRCihHDdAKHYGeiTIZyHQ8fyK3NL8yEoRzRoIlCPEefCQgh/no6hg==
dependencies:
"@types/cheerio" "^0.22.13"
"@types/elliptic" "^6.4.10"
"@types/node" "^12.7.12"
"@types/randombytes" "^2.0.0"
ajv "^4.11.5"
bip39 "^3.0.2"
bitcoinjs-lib "^5.1.6"
bn.js "^4.11.8"
cross-fetch "^3.0.4"
elliptic "^6.5.1"
form-data "^2.5.1"
jsontokens "3.0.0-alpha.0"
query-string "^6.8.3"
randombytes "^2.1.0"
request "^2.88.0"
ripemd160-min "0.0.5"
schema-inspector "^1.6.8"
tslib "^1.10.0"
uuid "^3.3.3"
zone-file "^1.0.0"
bluebird@3.5.5:
version "3.5.5"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
@@ -11923,6 +11970,18 @@ jsontokens@3.0.0, jsontokens@^3.0.0:
elliptic "^6.4.1"
sha.js "^2.4.11"
jsontokens@3.0.0-alpha.0:
version "3.0.0-alpha.0"
resolved "https://registry.yarnpkg.com/jsontokens/-/jsontokens-3.0.0-alpha.0.tgz#16d04a2019a6dbe2392e4eeb489ac47ec3d855d7"
integrity sha512-+2JdFr2d3XBfamUnETQdv76u3AwBl8jmLp2Hgb/uK5NTys0FpoR5KbIIqv3QrXtjn0uIIPJz9JmhqDAnXd2Nhg==
dependencies:
"@types/elliptic" "^6.4.9"
asn1.js "^5.0.1"
base64url "^3.0.1"
ecdsa-sig-formatter "^1.0.11"
elliptic "^6.4.1"
key-encoder "^2.0.3"
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -11941,6 +12000,16 @@ jsx-ast-utils@^2.2.3, jsx-ast-utils@^2.4.1:
array-includes "^3.1.1"
object.assign "^4.1.0"
key-encoder@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/key-encoder/-/key-encoder-2.0.3.tgz#77073bb48ff1fe2173bb2088b83b91152c8fa4ba"
integrity sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg==
dependencies:
"@types/elliptic" "^6.4.9"
asn1.js "^5.0.1"
bn.js "^4.11.8"
elliptic "^6.4.1"
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"