# kickstart-for-time-pwa ## Project setup We have pkgx.dev set up in package.json, so you can use `dev` to set up the dev environment. ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",` ``` npm run build ``` ### Lints and fixes files ``` npm run lint ``` ## Tests ### For your own web-push tests, change the 'vapid' URL in App.vue, and install apps on the same domain. ### Test key contents See [this page](openssl_signing_console.rst) ### Register new user on test server New users require registration. This can be done with a claim payload like this by an existing user: ``` const vcClaim = { "@context": "https://schema.org", "@type": "RegisterAction", agent: { identifier: identity0.did }, object: SERVICE_ID, participant: { identifier: newIdentity.did }, }; ``` On the test server, User #0 has rights to register others, so you can start playing one of two ways: - Import the keys for the test User `did:ethr:0x000Ee5654b9742f6Fe18ea970e32b97ee2247B51` by importing this seed phrase: `seminar accuse mystery assist delay law thing deal image undo guard initial shallow wrestle list fragile borrow velvet tomorrow awake explain test offer control` (Other test users are found [here](https://github.com/trentlarson/endorser-ch/blob/master/test/util.js).) - Alternatively, register someone else under User #0 automatically: * In the `src/views/AccountViewView.vue` file, uncomment the lines referring to "testServerRegisterUser". * Visit the `/account` page. ### Create multiple identifiers Go to /start and create or import a new one. Then switch identifiers on the bottom of the Your Identity page. ### Create keys with alternate tools See [this page](openssl_signing_console.rst) ### Customize Vue configuration See [Configuration Reference](https://cli.vuejs.org/config/). ## Scenarios - Create a new identity as prompted. Go to "Your Identity" screen and copy the ID to the clipboard. - Go back to /start and import test User `did:ethr:0x000Ee5654b9742f6Fe18ea970e32b97ee2247B51` with this this seed phrase: `seminar accuse mystery assist delay law thing deal image undo guard initial shallow wrestle list fragile borrow velvet tomorrow awake explain test offer control` (Other test users are found [here](https://github.com/trentlarson/endorser-ch/blob/master/test/util.js).) - Go to "Your Contacts" screen and add the ID you copied to the clipboard, and hit "+" to add them. - Click on the "Registration Unknown" button and register that person to be able to make claims as them. ### Clear data & restart * Clear cache for localhost, then go to http://localhost:8080/start (because it'll generate a new one automatically if you start on the `/account` page). * Unregister service worker (in Chrome, go to `chrome://serviceworker-internals/`; in Firefox, go to `about:serviceworkers` or `about:debugging`). * Clear notifications (in Chrome, go to `chrome://settings/content/notifications`; in Firefox, go to `about:preferences` and search). ## Other ### Reference Material * Notifications can be type of `toast` (self-dismiss), `info`, `success`, `warning`, and `danger`. They are done via [notiwind](https://www.npmjs.com/package/notiwind) and set up in App.vue. ``` // reference material from https://github.com/trentlarson/endorser-mobile/blob/8dc8e0353e0cc80ffa7ed89ded15c8b0da92726b/src/utility/idUtility.ts#L83 // Import an existing ID export const importAndStoreIdentifier = async (mnemonic: string, mnemonicPassword: string, toLowercase: boolean, previousIdentifiers: Array) => { // just to get rid of variability that might cause an error mnemonic = mnemonic.trim().toLowerCase() /** // an approach I pieced together // requires: yarn add elliptic // ... plus: // const EC = require('elliptic').ec // const secp256k1 = new EC('secp256k1') // const keyHex: string = bip39.mnemonicToEntropy(mnemonic) // returns a KeyPair from the elliptic.ec library const keyPair = secp256k1.keyFromPrivate(keyHex, 'hex') // this code is from did-provider-eth createIdentifier const privateHex = keyPair.getPrivate('hex') const publicHex = keyPair.getPublic('hex') const address = didJwt.toEthereumAddress(publicHex) **/ /** // from https://github.com/uport-project/veramo/discussions/346#discussioncomment-302234 // ... which almost works but the didJwt.toEthereumAddress is wrong // requires: yarn add bip32 // ... plus: import * as bip32 from 'bip32' // const seed: Buffer = await bip39.mnemonicToSeed(mnemonic) const root = bip32.fromSeed(seed) const node = root.derivePath(UPORT_ROOT_DERIVATION_PATH) const privateHex = node.privateKey.toString("hex") const publicHex = node.publicKey.toString("hex") const address = didJwt.toEthereumAddress('0x' + publicHex) **/ /** // from https://github.com/uport-project/veramo/discussions/346#discussioncomment-302234 // requires: yarn add @ethersproject/hdnode // ... plus: import { HDNode } from '@ethersproject/hdnode' **/ const hdnode: HDNode = HDNode.fromMnemonic(mnemonic) const rootNode: HDNode = hdnode.derivePath(UPORT_ROOT_DERIVATION_PATH) const privateHex = rootNode.privateKey.substring(2) // original starts with '0x' const publicHex = rootNode.publicKey.substring(2) // original starts with '0x' let address = rootNode.address const prevIds = previousIdentifiers || []; if (toLowercase) { const foundEqual = R.find( (id) => utility.rawAddressOfDid(id.did) === address, prevIds ) if (foundEqual) { // They're trying to create a lowercase version of one that exists in normal case. // (We really should notify the user.) appStore.dispatch(appSlice.actions.addLog({log: true, msg: "Will create a normal-case version of the DID since a regular version exists."})) } else { address = address.toLowerCase() } } else { // They're not trying to convert to lowercase. const foundLower = R.find((id) => utility.rawAddressOfDid(id.did) === address.toLowerCase(), prevIds ) if (foundLower) { // They're trying to create a normal case version of one that exists in lowercase. // (We really should notify the user.) appStore.dispatch(appSlice.actions.addLog({log: true, msg: "Will create a lowercase version of the DID since a lowercase version exists."})) address = address.toLowerCase() } } appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... derived keys and address..."})) const newId = newIdentifier(address, publicHex, privateHex, UPORT_ROOT_DERIVATION_PATH) appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... created new ID..."})) // awaiting because otherwise the UI may not see that a mnemonic was created const savedId = await storeIdentifier(newId, mnemonic, mnemonicPassword) appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... stored new ID..."})) return savedId } // Create a totally new ID export const createAndStoreIdentifier = async (mnemonicPassword) => { // This doesn't give us the entropy/seed. //const id = await agent.didManagerCreate() const entropy = crypto.randomBytes(32) const mnemonic = bip39.entropyToMnemonic(entropy) appStore.dispatch(appSlice.actions.addLog({log: false, msg: "... generated mnemonic..."})) return importAndStoreIdentifier(mnemonic, mnemonicPassword, false, []) } ``` ## Kudos Gifts make the world go 'round! * [Máximo Fernández](https://medium.com/@maxfarenas) for the 3D [code](https://github.com/maxfer03/vue-three-ns) and [explanatory post](https://medium.com/nicasource/building-an-interactive-web-portfolio-with-vue-three-js-part-three-implementing-three-js-452cb375ef80) * [Many tools & libraries]() such as Nodejs.org, IntelliJ Idea, Veramo.io, Vuejs.org, threejs.org * [Bush 3D model](https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439) * [Forest floor image](https://www.goodfreephotos.com/albums/textures/leafy-autumn-forest-floor.jpg)