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

feat: add capabilities.getAll() #326

Merged
merged 4 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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'
)
})