Skip to content

Commit

Permalink
generate function for asking id (#581)
Browse files Browse the repository at this point in the history
* generate function for asking id

* asking with more commands

* change message

* fix issue for asking repeatly

* update test

* fix identity setting

* change config

* change back

Co-authored-by: Mike Purvis <mikedotexe@gmail.com>
  • Loading branch information
icerove and mikedotexe committed Nov 3, 2020
1 parent 496ed57 commit 6a71513
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 43 deletions.
3 changes: 3 additions & 0 deletions commands/add-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const exitOnError = require('../utils/exit-on-error');
const connect = require('../utils/connect');
const inspectResponse = require('../utils/inspect-response');
const { utils } = require('near-api-js');
const eventtracking = require('../utils/eventtracking');


module.exports = {
command: 'add-key <account-id> <access-key>',
Expand Down Expand Up @@ -31,6 +33,7 @@ module.exports = {
};

async function addAccessKey(options) {
await eventtracking.askForId(options);
console.log(`Adding ${options.contractId ? 'function call access' : 'full access'} key = ${options.accessKey} to ${options.accountId}.`);
const near = await connect(options);
const account = await near.account(options.accountId);
Expand Down
2 changes: 2 additions & 0 deletions commands/delete-key.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const exitOnError = require('../utils/exit-on-error');
const connect = require('../utils/connect');
const inspectResponse = require('../utils/inspect-response');
const eventtracking = require('../utils/eventtracking');

module.exports = {
command: 'delete-key <account-id> <access-key>',
Expand All @@ -15,6 +16,7 @@ module.exports = {
};

async function deleteAccessKey(options) {
await eventtracking.askForId(options);
console.log(`Deleting key = ${options.accessKey} on ${options.accountId}.`);
const near = await connect(options);
const account = await near.account(options.accountId);
Expand Down
71 changes: 38 additions & 33 deletions commands/generate-key.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,50 @@
const KeyPair = require('near-api-js').KeyPair;
const exitOnError = require('../utils/exit-on-error');
const implicitAccountId = require('../utils/implicit-accountid');
const connect = require('../utils/connect');
const eventtracking = require('../utils/eventtracking');

module.exports = {
command: 'generate-key [account-id]',
desc: 'generate key or show key from Ledger',
builder: (yargs) => yargs,
handler: exitOnError(async (argv) => {
let near = await require('../utils/connect')(argv);
handler: exitOnError(generateKey)
};

if (argv.usingLedger) {
if (argv.accountId) {
console.log('WARN: Account id is provided but ignored in case of using Ledger.');
}
const publicKey = await argv.signer.getPublicKey();
// NOTE: Command above already prints public key.
console.log(`Implicit account: ${implicitAccountId(publicKey.toString())}`);
// TODO: query all accounts with this public key here.
// TODO: check if implicit account exist, and if the key doen't match already.
return;
}
async function generateKey(options) {
await eventtracking.askForId(options);
const near = await connect(options);

const { deps: { keyStore } } = near.config;
const existingKey = await keyStore.getKey(argv.networkId, argv.accountId);
if (existingKey) {
console.log(`Account has existing key pair with ${existingKey.publicKey} public key`);
return;
if (options.usingLedger) {
if (options.accountId) {
console.log('WARN: Account id is provided but ignored in case of using Ledger.');
}
const publicKey = await options.signer.getPublicKey();
// NOTE: Command above already prints public key.
console.log(`Implicit account: ${implicitAccountId(publicKey.toString())}`);
// TODO: query all accounts with this public key here.
// TODO: check if implicit account exist, and if the key doesn't match already.
return;
}

// If key doesn't exist, create one and store in the keyStore.
// Otherwise, it's expected that both key and accountId are already provided in arguments.
if (!argv.publicKey) {
const keyPair = KeyPair.fromRandom('ed25519');
argv.publicKey = keyPair.publicKey.toString();
argv.accountId = argv.accountId || implicitAccountId(argv.publicKey);
await keyStore.setKey(argv.networkId, argv.accountId, keyPair);
} else if (argv.seedPhrase) {
const seededKeyPair = await argv.signer.keyStore.getKey(argv.networkId, argv.accountId);
await keyStore.setKey(argv.networkId, argv.accountId, seededKeyPair);
}

console.log(`Key pair with ${argv.publicKey} public key for an account "${argv.accountId}"`);
})
};
const { deps: { keyStore } } = near.config;
const existingKey = await keyStore.getKey(options.networkId, options.accountId);
if (existingKey) {
console.log(`Account has existing key pair with ${existingKey.publicKey} public key`);
return;
}

// If key doesn't exist, create one and store in the keyStore.
// Otherwise, it's expected that both key and accountId are already provided in arguments.
if (!options.publicKey) {
const keyPair = KeyPair.fromRandom('ed25519');
options.publicKey = keyPair.publicKey.toString();
options.accountId = options.accountId || implicitAccountId(options.publicKey);
await keyStore.setKey(options.networkId, options.accountId, keyPair);
} else if (options.seedPhrase) {
const seededKeyPair = await options.signer.keyStore.getKey(options.networkId, options.accountId);
await keyStore.setKey(options.networkId, options.accountId, seededKeyPair);
}

console.log(`Key pair with ${options.publicKey} public key for an account "${options.accountId}"`);
}
8 changes: 7 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ exports.clean = async function () {
};

exports.deploy = async function (options) {
await eventtracking.askForId(options);
console.log(
`Starting deployment. Account id: ${options.accountId}, node: ${options.nodeUrl}, helper: ${options.helperUrl}, file: ${options.wasmFile}`);

Expand Down Expand Up @@ -82,6 +83,7 @@ const openUrl = async function(url) {

exports.login = async function (options) {
await eventtracking.askForConsentIfNeeded(options);
await eventtracking.askForId(options);
if (!options.walletUrl) {
console.log('Log in is not needed on this environment. Please use appropriate master account for shell operations.');
await eventtracking.track(eventtracking.EVENT_ID_LOGIN_END, { success: true, login_is_not_needed: true }, options);
Expand Down Expand Up @@ -176,6 +178,7 @@ exports.login = async function (options) {
};

exports.viewAccount = async function (options) {
await eventtracking.askForId(options);
let near = await connect(options);
let account = await near.account(options.accountId);
let state = await account.state();
Expand All @@ -187,7 +190,7 @@ exports.viewAccount = async function (options) {
};

exports.deleteAccount = async function (options) {

await eventtracking.askForId(options);
console.log(
`Deleting account. Account id: ${options.accountId}, node: ${options.nodeUrl}, helper: ${options.helperUrl}, beneficiary: ${options.beneficiaryId}`);
const near = await connect(options);
Expand All @@ -198,6 +201,7 @@ exports.deleteAccount = async function (options) {
};

exports.keys = async function (options) {
await eventtracking.askForId(options);
let near = await connect(options);
let account = await near.account(options.accountId);
let accessKeys = await account.getAccessKeys();
Expand All @@ -206,6 +210,7 @@ exports.keys = async function (options) {
};

exports.sendMoney = async function (options) {
await eventtracking.askForId(options);
console.log(`Sending ${options.amount} NEAR to ${options.receiver} from ${options.sender}`);
const near = await connect(options);
const account = await near.account(options.sender);
Expand All @@ -214,6 +219,7 @@ exports.sendMoney = async function (options) {
};

exports.stake = async function (options) {
await eventtracking.askForId(options);
console.log(`Staking ${options.amount} (${utils.format.parseNearAmount(options.amount)}) on ${options.accountId} with public key = ${qs.unescape(options.stakingKey)}.`);
const near = await connect(options);
const account = await near.account(options.accountId);
Expand Down
2 changes: 1 addition & 1 deletion test/test_account_operations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ echo Create account
../bin/near create-account $testaccount

echo Get account state
RESULT=$(../bin/near state $testaccount -v | ../node_modules/.bin/strip-ansi)
RESULT=$(yes | ../bin/near state $testaccount -v | ../node_modules/.bin/strip-ansi)
echo $RESULT
EXPECTED=".+Account $testaccount.+amount:.+'100000000000000000000000000'.+ "
if [[ ! "$RESULT" =~ $EXPECTED ]]; then
Expand Down
4 changes: 2 additions & 2 deletions test/test_generate_key.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ KEY_FILE=~/.near-credentials/$NODE_ENV/generate-key-test.json
rm -f "$KEY_FILE"
echo "Testing generating-key: new key"

RESULT=$(./bin/near generate-key generate-key-test --networkId $NODE_ENV -v)
RESULT=$(yes |./bin/near generate-key generate-key-test --networkId $NODE_ENV -v)
echo $RESULT

if [[ ! -f "${KEY_FILE}" ]]; then
Expand All @@ -21,7 +21,7 @@ fi

echo "Testing generating-key: key for account already exists"

RESULT2=$(./bin/near generate-key generate-key-test --networkId $NODE_ENV -v)
RESULT2=$(yes |./bin/near generate-key generate-key-test --networkId $NODE_ENV -v)
echo $RESULT2

EXPECTED2=".*Account has existing key pair with ed25519:.+ public key.*"
Expand Down
79 changes: 73 additions & 6 deletions utils/eventtracking.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const uuid = require('uuid');

const TRACKING_ENABLED_KEY = 'trackingEnabled';
const TRACKING_SESSION_ID_KEY = 'trackingSessionId';
const TRACKING_ID_KEY = 'trackingID';

const isGitPod = () => {
return !!process.env.GITPOD_WORKSPACE_URL;
Expand Down Expand Up @@ -40,6 +41,20 @@ const shouldTrack = (shellSettings) => {
);
};

const shouldTrackID = (shellSettings) => {
return (
TRACKING_ID_KEY in shellSettings &&
shellSettings[TRACKING_ID_KEY]
);
};

const shouldNOTTrackID = (shellSettings) => {
return (
TRACKING_ID_KEY in shellSettings &&
!shellSettings[TRACKING_ID_KEY]
);
};

const track = async (eventType, eventProperties, options) => {
const shellSettings = settings.getShellSettings();
if (!shouldTrack(shellSettings)) {
Expand All @@ -60,10 +75,8 @@ const track = async (eventType, eventProperties, options) => {
};
Object.assign(mixPanelProperties, eventProperties);
await Promise.all([mixpanel.track(eventType, mixPanelProperties),
mixpanel.people.set_once({
distinct_id: isGitPod()
? getGitPodUserHash()
: shellSettings[TRACKING_SESSION_ID_KEY],
mixpanel.people.set(mixPanelProperties.distinct_id, {
deployed_contracts: 0,
network_id: options.networkId,
node_url: options.nodeUrl,
})]);
Expand All @@ -85,7 +98,7 @@ const getEventTrackingConsent = async () => {
const answer = await new Promise((resolve) => {
rl.question(
chalk`We would like to collect data on near-cli usage to improve developer experience.` +
chalk` We will never send private information. We only collect which commands are run via an anonymous identifier.` +
chalk` We will never send private information. We only collect which commands are run with attributes.` +
chalk`{bold.yellow Would you like to opt in (y/n)? }`,
async (consentToEventTracking) => {
if (consentToEventTracking.toLowerCase() == 'y') {
Expand All @@ -109,6 +122,57 @@ const getEventTrackingConsent = async () => {
}
};

const getIdTrackingConsent = async () => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
try {
for (let attempts = 0; attempts < 3; attempts++) {
const answer = await new Promise((resolve) => {
rl.question(
chalk`We would like to help with your development journey with NEAR.` +
chalk` We will ask you to share your account Id while using command. ` +
chalk`{bold.yellow Would you like to share the account Id (y/n)? }`,
async (consentToEventTracking) => {
if (consentToEventTracking.toLowerCase() == 'y') {
resolve(true);
} else if (
consentToEventTracking.toLowerCase() == 'n'
) {
resolve(false);
}
resolve(undefined);
}
);
});
if (answer !== undefined) {
return answer;
}
}
return false; // If they can't figure it out in this many attempts, just opt out
} finally {
rl.close();
}
};

const askForId = async (options) => {
const shellSettings = settings.getShellSettings();
if(shouldTrackID(shellSettings)){
const id = isGitPod() ? getGitPodUserHash() : shellSettings[TRACKING_SESSION_ID_KEY];
await Promise.all([
mixpanel.alias(options.accountId, id),
mixpanel.people.set(id, {account_id: options.accountId})
]);
}else if(shouldNOTTrackID(shellSettings)){
return;
}
else{
shellSettings[TRACKING_ID_KEY] = (await getIdTrackingConsent());
settings.saveShellSettings(shellSettings);
}
};

const askForConsentIfNeeded = async (options) => {
const shellSettings = settings.getShellSettings();
// if the appropriate option is not in settings, ask now and save settings.
Expand All @@ -128,13 +192,16 @@ const askForConsentIfNeeded = async (options) => {
};

const trackDeployedContract = async () => {
await mixpanel.people.increment({deployed_contracts: 1});
const shellSettings = settings.getShellSettings();
const id = isGitPod() ? getGitPodUserHash() : shellSettings[TRACKING_SESSION_ID_KEY];
await mixpanel.people.increment(id, 'deployed_contracts');
};

module.exports = {
track,
askForConsentIfNeeded,
trackDeployedContract,
askForId,
// Some of the event ids are auto-generated runtime with the naming convention event_id_shell_{command}_start

EVENT_ID_CREATE_ACCOUNT_END: 'event_id_shell_create-account_end',
Expand Down

0 comments on commit 6a71513

Please sign in to comment.