Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: write own device info when creating and adding projects #297

Merged
merged 21 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
669bda5
fix: write device info to projects when instatiating
achou11 Oct 2, 2023
48b6f37
expose method on project for manager to call instead
achou11 Oct 3, 2023
aabd9c5
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 3, 2023
5ee69d1
fix device info tests failing
achou11 Oct 3, 2023
d1d11ba
replace unit tests for MemberApi with e2e for member namespace
achou11 Oct 3, 2023
decf3df
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 3, 2023
98d9f8c
simplify device info tests
achou11 Oct 4, 2023
f5faf8c
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 4, 2023
4d16c83
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 4, 2023
978b2e9
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 4, 2023
e057b4f
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 4, 2023
03b0f75
fix failing device info test
achou11 Oct 4, 2023
9582f71
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 5, 2023
3da7966
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 5, 2023
0eb9bd8
update getById and getMany methods in member api
achou11 Oct 5, 2023
b2389bf
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 9, 2023
c0a7fa1
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 9, 2023
2231458
Merge branch 'main' into 289/project-device-info-init
achou11 Oct 9, 2023
81e8d7b
make setOwnDeviceInfo private symbol prop
achou11 Oct 10, 2023
1a62ea8
update member api constructor deviceKey prop to deviceId
achou11 Oct 10, 2023
b98fb79
remove todo comment
achou11 Oct 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/mapeo-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ export class MapeoManager {
sharedDb: this.#db,
sharedIndexWriter: this.#projectSettingsIndexWriter,
rpc: this.#rpc,
getLocalDeviceInfo: this.getDeviceInfo.bind(this),
})

// 5. Write project name and any other relevant metadata to project instance
Expand Down Expand Up @@ -265,6 +266,7 @@ export class MapeoManager {
sharedDb: this.#db,
sharedIndexWriter: this.#projectSettingsIndexWriter,
rpc: this.#rpc,
getLocalDeviceInfo: this.getDeviceInfo.bind(this),
})

// 3. Keep track of project instance as we know it's a properly existing project
Expand Down
72 changes: 71 additions & 1 deletion src/mapeo-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const kCapabilities = Symbol('capabilities')

export class MapeoProject {
#projectId
#deviceId
#coreManager
#dataStores
#dataTypes
Expand All @@ -48,6 +49,7 @@ export class MapeoProject {
#coreOwnership
#capabilities
#ownershipWriteDone
#ownDeviceInfoWriteDone
achou11 marked this conversation as resolved.
Show resolved Hide resolved
#memberApi

/**
Expand All @@ -61,6 +63,7 @@ export class MapeoProject {
* @param {IndexWriter} opts.sharedIndexWriter
* @param {import('./types.js').CoreStorage} opts.coreStorage Folder to store all hypercore data
* @param {import('./rpc/index.js').MapeoRPC} opts.rpc
* @param {() => Promise<Partial<Pick<import('@mapeo/schema').DeviceInfoValue, 'name'>>>} opts.getLocalDeviceInfo
achou11 marked this conversation as resolved.
Show resolved Hide resolved
*
*/
constructor({
Expand All @@ -73,6 +76,7 @@ export class MapeoProject {
projectSecretKey,
encryptionKeys,
rpc,
getLocalDeviceInfo,
}) {
this.#projectId = projectKeyToId(projectKey)

Expand Down Expand Up @@ -103,13 +107,20 @@ export class MapeoProject {
storage: coreManagerStorage,
sqlite,
})

// TODO: Use discovery key
achou11 marked this conversation as resolved.
Show resolved Hide resolved
this.#deviceId = this.#coreManager
.getWriterCore('config')
.key.toString('hex')

const indexWriter = new IndexWriter({
tables: [
observationTable,
presetTable,
fieldTable,
coreOwnershipTable,
roleTable,
deviceInfoTable,
],
sqlite,
getWinner,
Expand Down Expand Up @@ -248,6 +259,54 @@ export class MapeoProject {
.then(deferred.resolve)
.catch(deferred.reject)
})

///////// 5. Write device info record for project instance instantiator

const deferredDeviceInfoWrite = pDefer()
// Avoid uncaught rejection. If this is rejected then project.ready() will reject
deferredDeviceInfoWrite.promise.catch(() => {})
this.#ownDeviceInfoWriteDone = deferredDeviceInfoWrite.promise

const configCore = this.#coreManager.getWriterCore('config').core
configCore.on('ready', async () => {
try {
const existing = await this.#dataTypes.deviceInfo.getByDocId(
this.#deviceId
)

if (existing) {
const { name } = await getLocalDeviceInfo()

// If the device info is different from what the project has, update it for the project
achou11 marked this conversation as resolved.
Show resolved Hide resolved
if (name && name !== existing.name) {
await this.#dataTypes.deviceInfo.update(existing.versionId, {
schemaName: 'deviceInfo',
name,
})
}

return deferredDeviceInfoWrite.resolve()
}
} catch (err) {
return deferredDeviceInfoWrite.reject(err)
}

// No existing device info record exists for instantiator in the project so attempt to write one
try {
const { name } = await getLocalDeviceInfo()

if (name) {
await this.#dataTypes.deviceInfo[kCreateWithDocId](this.#deviceId, {
schemaName: 'deviceInfo',
name,
})
}

return deferredDeviceInfoWrite.resolve()
} catch (err) {
return deferredDeviceInfoWrite.reject(err)
}
})
}

/**
Expand All @@ -268,7 +327,11 @@ export class MapeoProject {
* Resolves when hypercores have all loaded
*/
async ready() {
await Promise.all([this.#coreManager.ready(), this.#ownershipWriteDone])
await Promise.all([
this.#coreManager.ready(),
this.#ownershipWriteDone,
this.#ownDeviceInfoWriteDone,
])
}

/**
Expand Down Expand Up @@ -318,6 +381,13 @@ export class MapeoProject {
return this.#memberApi
}

/**
* @returns {string} id representing the device using this project instance
*/
get deviceId() {
return this.#deviceId
}

achou11 marked this conversation as resolved.
Show resolved Hide resolved
/**
* @param {Partial<EditableProjectSettings>} settings
* @returns {Promise<EditableProjectSettings>}
Expand Down
53 changes: 52 additions & 1 deletion test-e2e/device-info.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { test } from 'brittle'
import { randomBytes } from 'crypto'
import { KeyManager } from '@mapeo/crypto'
import { MapeoManager } from '../src/mapeo-manager.js'
import RAM from 'random-access-memory'

import { MapeoManager } from '../src/mapeo-manager.js'

test('write and read deviceInfo', async (t) => {
const rootKey = KeyManager.generateRootKey()
const manager = new MapeoManager({
Expand All @@ -19,3 +21,52 @@ test('write and read deviceInfo', async (t) => {
const readInfo2 = await manager.getDeviceInfo()
t.alike(readInfo2, info2)
})

test('device info written to projects', async (t) => {
const manager = new MapeoManager({
rootKey: KeyManager.generateRootKey(),
dbFolder: ':memory:',
coreStorage: () => new RAM(),
})

await manager.setDeviceInfo({ name: 'mapeo' })

t.test('creating project', async (st) => {
const projectId = await manager.createProject()
const project = await manager.getProject(projectId)

await project.ready()

const members = await project.$member.getMany()

st.is(members.length, 1)

const member = members[0]

st.is(member.deviceId, project.deviceId)
st.is(member.name, (await manager.getDeviceInfo())?.name)
})

t.test('adding project', async (st) => {
const projectId = await manager.addProject({
projectKey: randomBytes(32),
encryptionKeys: { auth: randomBytes(32) },
})

const project = await manager.getProject(projectId)

await project.ready()

const members = await project.$member.getMany()

st.is(members.length, 1)

const member = members[0]

st.is(member.deviceId, project.deviceId)
st.is(member.name, (await manager.getDeviceInfo())?.name)
})

// TODO: Test changing device info and checking device info per project afterwards
// TODO: Test closing project, changing name, and getting project to see if device info for project is updated
})
1 change: 1 addition & 0 deletions test-types/data-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const mapeoProject = new MapeoProject({
sqlite,
}),
rpc: new MapeoRPC(),
getLocalDeviceInfo: async () => ({ name: 'foo' }),
})

///// Observations
Expand Down
Loading