Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Add a writer explictly.

Get the local writer key.

#### `inv = await pass.createInvite()`
#### `inv = await pass.createInvite({ readOnly })`

Get invite to add a writer.

Expand Down
33 changes: 28 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,27 @@ class AutopassPairer extends ReadyResource {

await this.pass.deleteInvite()
}
const readOnly = JSON.parse(result.data.toString()).readOnly
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

curios question,

in the confirm side, we set an object of { key, encryptionKey, additional }

where is the result.data coming from?

Copy link
Copy Markdown
Member Author

@supersuryaansh supersuryaansh Feb 11, 2026

Choose a reason for hiding this comment

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

it comes from additional field

this.swarm = null
this.store = null
if (this.onresolve) this._whenWritable()
if (this.onresolve && readOnly) {
this._whenReadable()
} else if (this.onresolve) {
this._whenWritable()
}
this.candidate.close().catch(noop)
}
})
}

_whenReadable() {
const check = () => {
this.pass.base.off('update', check)
this.onresolve(this.pass)
}
this.pass.base.on('update', check)
}

_whenWritable() {
if (this.pass.base.writable) return
const check = () => {
Expand Down Expand Up @@ -238,14 +251,20 @@ class Autopass extends ReadyResource {

async createInvite(opts) {
if (this.opened === false) await this.ready()
const readOnly = opts?.readOnly ? true : false
const existing = await this.base.view.findOne('@autopass/invite', {})
if (existing) {
if (this.member) await this.member.flushed()
return z32.encode(existing.invite)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

we should check the existing invite to distinguish readonly and normal invite

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

bug case

  • create a normal invite
  • create a normal invite => return existing => CORRECT
  • create a readonly invite => return existing => INCORRECT because the existing is normal invite

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks for flagging, makes sense since invites are deleted only once they are used, let me fix this so the flow is correct.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

updated

}
const { id, invite, publicKey, expires } = BlindPairing.createInvite(this.base.key)
const { id, invite, publicKey, expires, additional } = BlindPairing.createInvite(
this.base.key,
{
data: Buffer.from(JSON.stringify({ readOnly }))
}
)

const record = { id, invite, publicKey, expires }
const record = { id, invite, publicKey, expires, readOnly, additional }
await this.base.append(encode('@autopass/add-invite', record))
if (this.member) await this.member.flushed()
return z32.encode(record.invite)
Expand Down Expand Up @@ -313,11 +332,15 @@ class Autopass extends ReadyResource {
if (inv === null || !b4a.equals(inv.id, id)) {
return
}
const readOnly = inv.readOnly
candidate.open(inv.publicKey)
await this.addWriter(candidate.userData)
if (!readOnly) {
await this.addWriter(candidate.userData)
}
candidate.confirm({
key: this.base.key,
encryptionKey: this.base.encryptionKey
encryptionKey: this.base.encryptionKey,
additional: inv.additional
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

curios question, why do we need to send this additional back to the other side?

don't see they use it in the other side

only see
result.key
result.encryptionKey
and result.data?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

result.data comes from additional field on the other side

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

it has information if the invite is read only or not, if it is read only then the base will wait just to check if pass is updating otherwise if its writeable it will wait to check if the pass has become writable before confirming that the device is paired

})
await this.deleteInvite()
}
Expand Down
18 changes: 18 additions & 0 deletions schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ template.register({
]
})

template.register({
name: 'additional-invite-data',
fields: [
{ name: 'data', type: 'buffer', required: true },
{ name: 'signature', type: 'buffer', required: true }
]
})

template.register({
name: 'invite',
compact: false,
Expand All @@ -85,6 +93,16 @@ template.register({
name: 'expires',
type: 'int',
required: true
},
{
name: 'readOnly',
type: 'bool',
required: true
},
{
name: 'additional',
type: '@autopass/additional-invite-data',
required: false
}
]
})
Expand Down
14 changes: 14 additions & 0 deletions spec/db/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"namespace": "autopass",
"id": 0,
"type": 1,
"version": 0,
"versionField": null,
"indexes": [],
"schema": "@autopass/records",
"derived": false,
Expand All @@ -18,6 +20,8 @@
"namespace": "autopass",
"id": 1,
"type": 1,
"version": 0,
"versionField": null,
"indexes": [],
"schema": "@autopass/invite",
"derived": false,
Expand All @@ -29,6 +33,8 @@
"namespace": "autopass",
"id": 2,
"type": 1,
"version": 0,
"versionField": null,
"indexes": [],
"schema": "@autopass/mirrors",
"derived": false,
Expand All @@ -40,6 +46,8 @@
"namespace": "autopass",
"id": 3,
"type": 1,
"version": 0,
"versionField": null,
"indexes": [],
"schema": "@autopass/writer",
"derived": false,
Expand All @@ -51,6 +59,8 @@
"namespace": "autopass",
"id": 4,
"type": 1,
"version": 0,
"versionField": null,
"indexes": [],
"schema": "@autopass/delete",
"derived": false,
Expand All @@ -62,6 +72,8 @@
"namespace": "autopass",
"id": 5,
"type": 1,
"version": 0,
"versionField": null,
"indexes": [],
"schema": "@autopass/del-invite",
"derived": false,
Expand All @@ -73,6 +85,8 @@
"namespace": "autopass",
"id": 6,
"type": 1,
"version": 0,
"versionField": null,
"indexes": [],
"schema": "@autopass/del-mirror",
"derived": false,
Expand Down
Loading