Skip to content

Commit

Permalink
Merge branch 'main' into feat/namespace-sync-state
Browse files Browse the repository at this point in the history
* main:
  feat: add capabilities.getAll() (#326)
  chore: gitignore .eslintcache (#327)
  • Loading branch information
gmaclennan committed Oct 8, 2023
2 parents 53472d9 + a51dc5c commit 39983ac
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ tmp
proto/build
/dist
!/drizzle/**/*.sql
.eslintcache
38 changes: 38 additions & 0 deletions src/capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,44 @@ export class Capabilities {
return capabilities
}

/**
* Get capabilities of all devices in the project. For your own device, if you
* have not yet synced your own role record, the "no role" capabilties is
* returned. The project creator will have `CREATOR_CAPABILITIES` unless a
* different role has been assigned.
*
* @returns {Promise<Record<string, Capability>>} Map of deviceId to Capability
*/
async getAll() {
const roles = await this.#dataType.getMany()
let projectCreatorDeviceId
try {
projectCreatorDeviceId = await this.#coreOwnership.getOwner(
this.#projectCreatorAuthCoreId
)
} catch (e) {
// Not found, we don't know who the project creator is so we can't include
// them in the returned map
}
/** @type {Record<string, Capability>} */
const capabilities = {}
for (const role of roles) {
const deviceId = role.docId
if (!isKnownRoleId(role.roleId)) continue
capabilities[deviceId] = DEFAULT_CAPABILITIES[role.roleId]
}
const includesSelf = Boolean(capabilities[this.#ownDeviceId])
if (!includesSelf) {
const isProjectCreator = this.#ownDeviceId === projectCreatorDeviceId
if (isProjectCreator) {
capabilities[this.#ownDeviceId] = CREATOR_CAPABILITIES
} else {
capabilities[this.#ownDeviceId] = NO_ROLE_CAPABILITIES
}
}
return capabilities
}

/**
* Assign a role to the specified `deviceId`. Devices without an assigned role
* are unable to sync, except the project creator that defaults to having all
Expand Down
68 changes: 68 additions & 0 deletions test-e2e/capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
DEFAULT_CAPABILITIES,
CREATOR_CAPABILITIES,
MEMBER_ROLE_ID,
COORDINATOR_ROLE_ID,
NO_ROLE_CAPABILITIES,
} from '../src/capabilities.js'
import { randomBytes } from 'crypto'

Expand Down Expand Up @@ -72,3 +74,69 @@ test('New device without capabilities', async (t) => {
await project[kCapabilities].assignRole(deviceId, MEMBER_ROLE_ID)
}, 'Trying to assign a role without capabilities throws an error')
})

test('getMany() - on invitor device', async (t) => {
const rootKey = KeyManager.generateRootKey()
const km = new KeyManager(rootKey)
const creatorDeviceId = km.getIdentityKeypair().publicKey.toString('hex')
const manager = new MapeoManager({
rootKey,
dbFolder: ':memory:',
coreStorage: () => new RAM(),
})

const projectId = await manager.createProject()
const project = await manager.getProject(projectId)
const ownCapabilities = await project.$getOwnCapabilities()

t.alike(
ownCapabilities,
CREATOR_CAPABILITIES,
'Project creator has creator capabilities'
)

const deviceId1 = randomBytes(32).toString('hex')
const deviceId2 = randomBytes(32).toString('hex')
await project[kCapabilities].assignRole(deviceId1, MEMBER_ROLE_ID)
await project[kCapabilities].assignRole(deviceId2, COORDINATOR_ROLE_ID)

const expected = {
[deviceId1]: DEFAULT_CAPABILITIES[MEMBER_ROLE_ID],
[deviceId2]: DEFAULT_CAPABILITIES[COORDINATOR_ROLE_ID],
[creatorDeviceId]: CREATOR_CAPABILITIES,
}

t.alike(
await project[kCapabilities].getAll(),
expected,
'expected capabilities'
)
})

test('getMany() - on newly invited device before sync', async (t) => {
const rootKey = KeyManager.generateRootKey()
const km = new KeyManager(rootKey)
const deviceId = km.getIdentityKeypair().publicKey.toString('hex')
const manager = new MapeoManager({
rootKey,
dbFolder: ':memory:',
coreStorage: () => new RAM(),
})

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

const expected = {
[deviceId]: NO_ROLE_CAPABILITIES,
}

t.alike(
await project[kCapabilities].getAll(),
expected,
'expected capabilities'
)
})

0 comments on commit 39983ac

Please sign in to comment.