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 9 commits
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
25 changes: 24 additions & 1 deletion src/mapeo-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,16 @@ export class MapeoManager {
// 5. Write project name and any other relevant metadata to project instance
await project.$setProjectSettings(settings)

// 6. Write device info into project
const deviceInfo = await this.getDeviceInfo()
if (deviceInfo.name) {
await project.$setOwnDeviceInfo({ name: deviceInfo.name })
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if this is desired behavior or not. keeping this means that one should call manager.setDeviceInfo() before adding or creating a project. Otherwise, attempting to use the $member namespace causes issues.


// TODO: Close the project instance instead of keeping it around
this.#activeProjects.set(projectPublicId, project)

// 6. Return project public id
// 7. Return project public id
return projectPublicId
}

Expand Down Expand Up @@ -365,6 +371,14 @@ export class MapeoManager {
projectInfo,
})

// 5. Write device info into project
const deviceInfo = await this.getDeviceInfo()

if (deviceInfo.name) {
const project = await this.getProject(projectPublicId)
await project.$setOwnDeviceInfo({ name: deviceInfo.name })
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same concern as previously stated regarding needing to call manager.setDeviceInfo() first


return projectPublicId
}

Expand All @@ -382,6 +396,15 @@ export class MapeoManager {
set: values,
})
.run()

const listedProjects = await this.listProjects()

await Promise.all(
listedProjects.map(async ({ projectId }) => {
const project = await this.getProject(projectId)
await project.$setOwnDeviceInfo(deviceInfo)
})
)
}

/**
Expand Down
33 changes: 33 additions & 0 deletions src/mapeo-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,15 @@ export class MapeoProject {
storage: coreManagerStorage,
sqlite,
})

const indexWriter = new IndexWriter({
tables: [
observationTable,
presetTable,
fieldTable,
coreOwnershipTable,
roleTable,
deviceInfoTable,
],
sqlite,
getWinner,
Expand Down Expand Up @@ -226,6 +228,7 @@ export class MapeoProject {

this.#memberApi = new MemberApi({
capabilities: this.#capabilities,
coreOwnership: this.#coreOwnership,
// @ts-expect-error
encryptionKeys,
projectKey,
Expand Down Expand Up @@ -273,6 +276,10 @@ export class MapeoProject {
return this.#capabilities
}

get deviceId() {
return this.#deviceId
}

/**
* Resolves when hypercores have all loaded
*/
Expand Down Expand Up @@ -376,6 +383,32 @@ export class MapeoProject {
async $getOwnCapabilities() {
return this.#capabilities.getCapabilities(this.#deviceId)
}

/**
* @param {Pick<import('@mapeo/schema').DeviceInfoValue, 'name'>} value
* @returns {Promise<import('@mapeo/schema').DeviceInfo>}
*/
async $setOwnDeviceInfo(value) {
achou11 marked this conversation as resolved.
Show resolved Hide resolved
const { deviceInfo } = this.#dataTypes

const configCoreId = this.#coreManager
.getWriterCore('config')
.key.toString('hex')

const existingDoc = await deviceInfo.getByDocId(configCoreId)

if (existingDoc) {
return deviceInfo.update(existingDoc.versionId, {
...value,
schemaName: 'deviceInfo',
})
}

return await deviceInfo[kCreateWithDocId](configCoreId, {
...value,
schemaName: 'deviceInfo',
})
}
}

/**
Expand Down
21 changes: 17 additions & 4 deletions src/member-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { projectKeyToId } from './utils.js'

export class MemberApi extends TypedEmitter {
#capabilities
#coreOwnership
#encryptionKeys
#projectKey
#rpc
Expand All @@ -16,16 +17,25 @@ export class MemberApi extends TypedEmitter {
/**
* @param {Object} opts
* @param {import('./capabilities.js').Capabilities} opts.capabilities
* @param {import('./core-ownership.js').CoreOwnership} opts.coreOwnership
* @param {import('./generated/keys.js').EncryptionKeys} opts.encryptionKeys
* @param {Buffer} opts.projectKey
* @param {import('./rpc/index.js').MapeoRPC} opts.rpc
* @param {Object} opts.dataTypes
* @param {Pick<DeviceInfoDataType, 'getByDocId' | 'getMany'>} opts.dataTypes.deviceInfo
* @param {Pick<ProjectDataType, 'getByDocId'>} opts.dataTypes.project
*/
constructor({ capabilities, encryptionKeys, projectKey, rpc, dataTypes }) {
constructor({
capabilities,
coreOwnership,
encryptionKeys,
projectKey,
rpc,
dataTypes,
}) {
super()
this.#capabilities = capabilities
this.#coreOwnership = coreOwnership
this.#encryptionKeys = encryptionKeys
this.#projectKey = projectKey
this.#rpc = rpc
Expand Down Expand Up @@ -55,6 +65,7 @@ export class MemberApi extends TypedEmitter {

if (response === InviteResponse_Decision.ACCEPT) {
await this.#capabilities.assignRole(deviceId, roleId)
// TODO: Write device info record
achou11 marked this conversation as resolved.
Show resolved Hide resolved
}

return response
Expand All @@ -65,7 +76,8 @@ export class MemberApi extends TypedEmitter {
* @returns {Promise<MemberInfo>}
*/
async getById(deviceId) {
const { name } = await this.#dataTypes.deviceInfo.getByDocId(deviceId)
const configCoreId = await this.#coreOwnership.getCoreId(deviceId, 'config')
const { name } = await this.#dataTypes.deviceInfo.getByDocId(configCoreId)
const capabilities = await this.#capabilities.getCapabilities(deviceId)
return { deviceId, name, capabilities }
}
Expand All @@ -77,9 +89,10 @@ export class MemberApi extends TypedEmitter {
const devices = await this.#dataTypes.deviceInfo.getMany()
return Promise.all(
devices.map(async ({ docId, name }) => {
const capabilities = await this.#capabilities.getCapabilities(docId)
const deviceId = await this.#coreOwnership.getOwner(docId)
const capabilities = await this.#capabilities.getCapabilities(deviceId)
return {
deviceId: docId,
deviceId,
name,
capabilities,
}
Expand Down
97 changes: 96 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,96 @@ test('write and read deviceInfo', async (t) => {
const readInfo2 = await manager.getDeviceInfo()
t.alike(readInfo2, info2)
})

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

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

const projectId = await manager.createProject()
const project = await manager.getProject(projectId)

await project.ready()

const me = await project.$member.getById(project.deviceId)

st.is(me.deviceId, project.deviceId)
st.alike({ name: me.name }, { name: 'mapeo' })
})

t.test('when adding project', async (st) => {
const manager = new MapeoManager({
rootKey: KeyManager.generateRootKey(),
dbFolder: ':memory:',
coreStorage: () => new RAM(),
})

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

const projectId = await manager.addProject({
projectKey: randomBytes(32),
encryptionKeys: { auth: randomBytes(32) },
})

const project = await manager.getProject(projectId)

await project.ready()

const me = await project.$member.getById(project.deviceId)

st.alike({ name: me.name }, { name: 'mapeo' })
})

t.test('after updating global device info', async (st) => {
const manager = new MapeoManager({
rootKey: KeyManager.generateRootKey(),
dbFolder: ':memory:',
coreStorage: () => new RAM(),
})

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

const projectIds = await Promise.all([
manager.createProject(),
manager.createProject(),
manager.createProject(),
])

const projects = await Promise.all(
projectIds.map(async (projectId) => {
const project = await manager.getProject(projectId)
await project.ready()
return project
})
)

{
const ownMemberInfos = await Promise.all(
projects.map((p) => p.$member.getById(p.deviceId))
)

for (const info of ownMemberInfos) {
st.alike({ name: info.name }, { name: 'before' })
}
}

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

{
const ownMemberInfos = await Promise.all(
projects.map((p) => p.$member.getById(p.deviceId))
)

for (const info of ownMemberInfos) {
st.alike({ name: info.name }, { name: 'after' })
}
}
})

// TODO: Test closing project, changing name, and getting project to see if device info for project is updated
})
Loading
Loading