blockstack-browser
blockstack-browser copied to clipboard
Stale profile.json (data loss)
Related to https://github.com/blockstack/blockstack-browser/issues/1745 @timstackblock and I decided to open a new issue for this since the bug(s) and solution(s) may be a superset of the original issue.
Several actions performed within a blockstack-browser tab cause a cached outdated profile.json to overwrite any changes made from another tab.
Reproducible Example
- Keep one or more blockstack-browser tabs open (likely common user behavior).
- Sign into a new blockstack app that uses the
publish_datascope - look up your profile.json to see the app added. e.g.:https://core.blockstack.org/v1/names/zone117x.id->https://gaia.blockstack.org/hub/1Nw25PemCRv24UQAcZdaj4uD11nkTCWRTE/profile.json - Go to an open tab and update something like your name, bio, a social proof, etc.
- Look up your profile.json again, see the app entry missing.
Potential Fix
Adherence to the single source of truth principle.
- Never use the cached localStorage profile object except for a few explicit cases (e.g. using the cached
namewhile it is pending registration, or display UI quickly while latest data is being fetched). - All writes to the profile should perform a
fetch latest->change only the intended profile fields->upload the updated profile. - These write actions include:
- Uploading avatar / profile pic.
- Updating human name.
- Updating bio / description.
- Adding an app entry (during auth with publish_data).
- Updating a social proof or btc/eth/pgp/ssh public key.
- The
fetch latest profilefunction should always attempt a fresh resolution starting with the source of truth (the identityaddress) so that name/zonefile updates are respected. - All areas performing gaia read & write operations should take into consideration that more than one identity/name can be used, and each identity can have its own gaiahub configuration. Operations on the selected identity should use the gaiahub config specific to that identity. Many areas essentially use gaiahub config for
identity[0]and/or whatever the global app state API setting happens to represent.
Demo code example
async function fetchProfile(identityAddress: string) {
/**
* First tries:
* 1. Fetch `names` for the `address` with `bitcoinAddressLookupUrl`.
* 2. Fetch zonefile with `nameLookupUrl` using the first name returned.
* 3. Parse zonefile for the `profile.json` url.
*
* In some situations one or more parts of this resolution chain will be
* unavailable. Examples:
* * Pending name registration (address but no name/zonefile available).
* * Legacy zonefile not containing a `profile.json` url.
* * Legacy `profile.json` file not containing a gaiaHubUrl.
*
* In these situations, the locally cached identities will be checked for a
* matching address which may contain the profile.json url if a name
* is pending registration.
*
* If still not found then the default gaiahub server is searched for a matching
* profile.json file at expected & legacy paths. The existing
* `fetchProfileLocations` function can be used for this.
*/
}
async function updateLatestProfile(updateCallback: (profile) => void) {
const profile = await fetchProfile()
updateCallback(profile)
await uploadProfile(profile)
}
async function updateProfileBio(desc: string) {
await updateLatestProfile(profile => {
profile.account.description = desc
})
}
async function updateAvatar(picUrl: string) {
await updateLatestProfile(profile => {
let imageObj = profile.image.find(img => img.name === 'avatar')
imageObj = Object.assign({}, DEFAULT_AVATAR, imageObj)
imageObj.contentUrl = picUrl
})
}
async function updateAppBucket(appDomain: string, appBucketUrl: string) {
await updateLatestProfile(profile => {
profile.apps[appDomain] = appBucketUrl
})
}
Stale UI in a tab
(This may need to be moved to a new issue)
Typical user behavior could involve having the blockstack-browser webpage open in a perma-tab. Or several tabs, in potentially different web browsers / devices.
The Page Visibility API https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API could be used to prevent a browser tab state from becoming stale.
A simple document.onvisibilitychange hook can be used to fetch possibly updated profile data, without having to perform something like constant polling.
@timstackblock Would you consider this bug P2 or P3?
@yknl I see you added this to a DX sprint a few months ago – was progress made?
@hstove are these fixes we can make implicitly as part of the rebuild?
The latest update to blockstack.js and Gaia that improves concurrency may help with this issue, but some browser changes are required. https://github.com/blockstack/blockstack.js/pull/743