@enbox/agent 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +2215 -0
- package/dist/browser.js.map +7 -0
- package/dist/browser.mjs +2215 -0
- package/dist/browser.mjs.map +7 -0
- package/dist/cjs/index.js +8530 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/agent-did-resolver-cache.js +87 -0
- package/dist/esm/agent-did-resolver-cache.js.map +1 -0
- package/dist/esm/bearer-identity.js +41 -0
- package/dist/esm/bearer-identity.js.map +1 -0
- package/dist/esm/connect.js +191 -0
- package/dist/esm/connect.js.map +1 -0
- package/dist/esm/crypto-api.js +346 -0
- package/dist/esm/crypto-api.js.map +1 -0
- package/dist/esm/did-api.js +278 -0
- package/dist/esm/did-api.js.map +1 -0
- package/dist/esm/dwn-api.js +336 -0
- package/dist/esm/dwn-api.js.map +1 -0
- package/dist/esm/dwn-registrar.js +120 -0
- package/dist/esm/dwn-registrar.js.map +1 -0
- package/dist/esm/hd-identity-vault.js +729 -0
- package/dist/esm/hd-identity-vault.js.map +1 -0
- package/dist/esm/identity-api.js +262 -0
- package/dist/esm/identity-api.js.map +1 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/local-key-manager.js +498 -0
- package/dist/esm/local-key-manager.js.map +1 -0
- package/dist/esm/oidc.js +507 -0
- package/dist/esm/oidc.js.map +1 -0
- package/dist/esm/permissions-api.js +322 -0
- package/dist/esm/permissions-api.js.map +1 -0
- package/dist/esm/prototyping/clients/dwn-rpc-types.js +2 -0
- package/dist/esm/prototyping/clients/dwn-rpc-types.js.map +1 -0
- package/dist/esm/prototyping/clients/dwn-server-info-cache-memory.js +74 -0
- package/dist/esm/prototyping/clients/dwn-server-info-cache-memory.js.map +1 -0
- package/dist/esm/prototyping/clients/http-dwn-rpc-client.js +105 -0
- package/dist/esm/prototyping/clients/http-dwn-rpc-client.js.map +1 -0
- package/dist/esm/prototyping/clients/json-rpc-socket.js +150 -0
- package/dist/esm/prototyping/clients/json-rpc-socket.js.map +1 -0
- package/dist/esm/prototyping/clients/json-rpc.js +58 -0
- package/dist/esm/prototyping/clients/json-rpc.js.map +1 -0
- package/dist/esm/prototyping/clients/server-info-types.js +2 -0
- package/dist/esm/prototyping/clients/server-info-types.js.map +1 -0
- package/dist/esm/prototyping/clients/web-socket-clients.js +90 -0
- package/dist/esm/prototyping/clients/web-socket-clients.js.map +1 -0
- package/dist/esm/prototyping/common/object.js +14 -0
- package/dist/esm/prototyping/common/object.js.map +1 -0
- package/dist/esm/prototyping/common/type-utils.js +2 -0
- package/dist/esm/prototyping/common/type-utils.js.map +1 -0
- package/dist/esm/prototyping/crypto/algorithms/aes-gcm.js +147 -0
- package/dist/esm/prototyping/crypto/algorithms/aes-gcm.js.map +1 -0
- package/dist/esm/prototyping/crypto/algorithms/aes-kw.js +137 -0
- package/dist/esm/prototyping/crypto/algorithms/aes-kw.js.map +1 -0
- package/dist/esm/prototyping/crypto/algorithms/ecdsa.js +307 -0
- package/dist/esm/prototyping/crypto/algorithms/ecdsa.js.map +1 -0
- package/dist/esm/prototyping/crypto/algorithms/eddsa.js +264 -0
- package/dist/esm/prototyping/crypto/algorithms/eddsa.js.map +1 -0
- package/dist/esm/prototyping/crypto/algorithms/hkdf.js +39 -0
- package/dist/esm/prototyping/crypto/algorithms/hkdf.js.map +1 -0
- package/dist/esm/prototyping/crypto/algorithms/pbkdf2.js +41 -0
- package/dist/esm/prototyping/crypto/algorithms/pbkdf2.js.map +1 -0
- package/dist/esm/prototyping/crypto/crypto-error.js +41 -0
- package/dist/esm/prototyping/crypto/crypto-error.js.map +1 -0
- package/dist/esm/prototyping/crypto/dsa.js +236 -0
- package/dist/esm/prototyping/crypto/dsa.js.map +1 -0
- package/dist/esm/prototyping/crypto/jose/jwe-compact.js +130 -0
- package/dist/esm/prototyping/crypto/jose/jwe-compact.js.map +1 -0
- package/dist/esm/prototyping/crypto/jose/jwe-flattened.js +294 -0
- package/dist/esm/prototyping/crypto/jose/jwe-flattened.js.map +1 -0
- package/dist/esm/prototyping/crypto/jose/jwe.js +308 -0
- package/dist/esm/prototyping/crypto/jose/jwe.js.map +1 -0
- package/dist/esm/prototyping/crypto/primitives/aes-gcm.js +352 -0
- package/dist/esm/prototyping/crypto/primitives/aes-gcm.js.map +1 -0
- package/dist/esm/prototyping/crypto/primitives/aes-kw.js +247 -0
- package/dist/esm/prototyping/crypto/primitives/aes-kw.js.map +1 -0
- package/dist/esm/prototyping/crypto/primitives/hkdf.js +80 -0
- package/dist/esm/prototyping/crypto/primitives/hkdf.js.map +1 -0
- package/dist/esm/prototyping/crypto/primitives/pbkdf2.js +85 -0
- package/dist/esm/prototyping/crypto/primitives/pbkdf2.js.map +1 -0
- package/dist/esm/prototyping/crypto/types/cipher.js +2 -0
- package/dist/esm/prototyping/crypto/types/cipher.js.map +1 -0
- package/dist/esm/prototyping/crypto/types/crypto-api.js +2 -0
- package/dist/esm/prototyping/crypto/types/crypto-api.js.map +1 -0
- package/dist/esm/prototyping/crypto/types/key-converter.js +2 -0
- package/dist/esm/prototyping/crypto/types/key-converter.js.map +1 -0
- package/dist/esm/prototyping/crypto/types/key-deriver.js +2 -0
- package/dist/esm/prototyping/crypto/types/key-deriver.js.map +1 -0
- package/dist/esm/prototyping/crypto/types/key-io.js +2 -0
- package/dist/esm/prototyping/crypto/types/key-io.js.map +1 -0
- package/dist/esm/prototyping/crypto/types/key-manager.js +2 -0
- package/dist/esm/prototyping/crypto/types/key-manager.js.map +1 -0
- package/dist/esm/prototyping/crypto/types/key-wrapper.js +2 -0
- package/dist/esm/prototyping/crypto/types/key-wrapper.js.map +1 -0
- package/dist/esm/prototyping/crypto/types/params-direct.js +2 -0
- package/dist/esm/prototyping/crypto/types/params-direct.js.map +1 -0
- package/dist/esm/prototyping/crypto/types/params-kms.js +2 -0
- package/dist/esm/prototyping/crypto/types/params-kms.js.map +1 -0
- package/dist/esm/prototyping/crypto/utils.js +19 -0
- package/dist/esm/prototyping/crypto/utils.js.map +1 -0
- package/dist/esm/prototyping/dids/resolver-cache-memory.js +77 -0
- package/dist/esm/prototyping/dids/resolver-cache-memory.js.map +1 -0
- package/dist/esm/prototyping/dids/utils.js +9 -0
- package/dist/esm/prototyping/dids/utils.js.map +1 -0
- package/dist/esm/rpc-client.js +123 -0
- package/dist/esm/rpc-client.js.map +1 -0
- package/dist/esm/store-data-protocols.js +38 -0
- package/dist/esm/store-data-protocols.js.map +1 -0
- package/dist/esm/store-data.js +320 -0
- package/dist/esm/store-data.js.map +1 -0
- package/dist/esm/store-did.js +136 -0
- package/dist/esm/store-did.js.map +1 -0
- package/dist/esm/store-identity.js +140 -0
- package/dist/esm/store-identity.js.map +1 -0
- package/dist/esm/store-key.js +136 -0
- package/dist/esm/store-key.js.map +1 -0
- package/dist/esm/sync-api.js +61 -0
- package/dist/esm/sync-api.js.map +1 -0
- package/dist/esm/sync-engine-level.js +618 -0
- package/dist/esm/sync-engine-level.js.map +1 -0
- package/dist/esm/test-harness.js +239 -0
- package/dist/esm/test-harness.js.map +1 -0
- package/dist/esm/types/agent.js +2 -0
- package/dist/esm/types/agent.js.map +1 -0
- package/dist/esm/types/dwn.js +31 -0
- package/dist/esm/types/dwn.js.map +1 -0
- package/dist/esm/types/identity-vault.js +2 -0
- package/dist/esm/types/identity-vault.js.map +1 -0
- package/dist/esm/types/identity.js +2 -0
- package/dist/esm/types/identity.js.map +1 -0
- package/dist/esm/types/key-manager.js +2 -0
- package/dist/esm/types/key-manager.js.map +1 -0
- package/dist/esm/types/permissions.js +2 -0
- package/dist/esm/types/permissions.js.map +1 -0
- package/dist/esm/types/sync.js +2 -0
- package/dist/esm/types/sync.js.map +1 -0
- package/dist/esm/types/vc.js +5 -0
- package/dist/esm/types/vc.js.map +1 -0
- package/dist/esm/utils-internal.js +147 -0
- package/dist/esm/utils-internal.js.map +1 -0
- package/dist/esm/utils.js +161 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/types/agent-did-resolver-cache.d.ts +30 -0
- package/dist/types/agent-did-resolver-cache.d.ts.map +1 -0
- package/dist/types/bearer-identity.d.ts +31 -0
- package/dist/types/bearer-identity.d.ts.map +1 -0
- package/dist/types/connect.d.ts +88 -0
- package/dist/types/connect.d.ts.map +1 -0
- package/dist/types/crypto-api.d.ts +286 -0
- package/dist/types/crypto-api.d.ts.map +1 -0
- package/dist/types/did-api.d.ts +119 -0
- package/dist/types/did-api.d.ts.map +1 -0
- package/dist/types/dwn-api.d.ts +66 -0
- package/dist/types/dwn-api.d.ts.map +1 -0
- package/dist/types/dwn-registrar.d.ts +29 -0
- package/dist/types/dwn-registrar.d.ts.map +1 -0
- package/dist/types/hd-identity-vault.d.ts +306 -0
- package/dist/types/hd-identity-vault.d.ts.map +1 -0
- package/dist/types/identity-api.d.ts +107 -0
- package/dist/types/identity-api.d.ts.map +1 -0
- package/dist/types/index.d.ts +30 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/local-key-manager.d.ts +311 -0
- package/dist/types/local-key-manager.d.ts.map +1 -0
- package/dist/types/oidc.d.ts +247 -0
- package/dist/types/oidc.d.ts.map +1 -0
- package/dist/types/permissions-api.d.ts +35 -0
- package/dist/types/permissions-api.d.ts.map +1 -0
- package/dist/types/prototyping/clients/dwn-rpc-types.d.ts +45 -0
- package/dist/types/prototyping/clients/dwn-rpc-types.d.ts.map +1 -0
- package/dist/types/prototyping/clients/dwn-server-info-cache-memory.d.ts +57 -0
- package/dist/types/prototyping/clients/dwn-server-info-cache-memory.d.ts.map +1 -0
- package/dist/types/prototyping/clients/http-dwn-rpc-client.d.ts +13 -0
- package/dist/types/prototyping/clients/http-dwn-rpc-client.d.ts.map +1 -0
- package/dist/types/prototyping/clients/json-rpc-socket.d.ts +43 -0
- package/dist/types/prototyping/clients/json-rpc-socket.d.ts.map +1 -0
- package/dist/types/prototyping/clients/json-rpc.d.ts +49 -0
- package/dist/types/prototyping/clients/json-rpc.d.ts.map +1 -0
- package/dist/types/prototyping/clients/server-info-types.d.ts +20 -0
- package/dist/types/prototyping/clients/server-info-types.d.ts.map +1 -0
- package/dist/types/prototyping/clients/web-socket-clients.d.ts +10 -0
- package/dist/types/prototyping/clients/web-socket-clients.d.ts.map +1 -0
- package/dist/types/prototyping/common/object.d.ts +2 -0
- package/dist/types/prototyping/common/object.d.ts.map +1 -0
- package/dist/types/prototyping/common/type-utils.d.ts +7 -0
- package/dist/types/prototyping/common/type-utils.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/algorithms/aes-gcm.d.ts +151 -0
- package/dist/types/prototyping/crypto/algorithms/aes-gcm.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/algorithms/aes-kw.d.ts +109 -0
- package/dist/types/prototyping/crypto/algorithms/aes-kw.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/algorithms/ecdsa.d.ts +160 -0
- package/dist/types/prototyping/crypto/algorithms/ecdsa.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/algorithms/eddsa.d.ts +157 -0
- package/dist/types/prototyping/crypto/algorithms/eddsa.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/algorithms/hkdf.d.ts +21 -0
- package/dist/types/prototyping/crypto/algorithms/hkdf.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/algorithms/pbkdf2.d.ts +21 -0
- package/dist/types/prototyping/crypto/algorithms/pbkdf2.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/crypto-error.d.ts +29 -0
- package/dist/types/prototyping/crypto/crypto-error.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/dsa.d.ts +169 -0
- package/dist/types/prototyping/crypto/dsa.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/jose/jwe-compact.d.ts +135 -0
- package/dist/types/prototyping/crypto/jose/jwe-compact.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/jose/jwe-flattened.d.ts +134 -0
- package/dist/types/prototyping/crypto/jose/jwe-flattened.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/jose/jwe.d.ts +378 -0
- package/dist/types/prototyping/crypto/jose/jwe.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/primitives/aes-gcm.d.ts +245 -0
- package/dist/types/prototyping/crypto/primitives/aes-gcm.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/primitives/aes-kw.d.ts +103 -0
- package/dist/types/prototyping/crypto/primitives/aes-kw.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/primitives/hkdf.d.ts +90 -0
- package/dist/types/prototyping/crypto/primitives/hkdf.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/primitives/pbkdf2.d.ts +84 -0
- package/dist/types/prototyping/crypto/primitives/pbkdf2.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/types/cipher.d.ts +14 -0
- package/dist/types/prototyping/crypto/types/cipher.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/types/crypto-api.d.ts +35 -0
- package/dist/types/prototyping/crypto/types/crypto-api.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/types/key-converter.d.ts +49 -0
- package/dist/types/prototyping/crypto/types/key-converter.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/types/key-deriver.d.ts +50 -0
- package/dist/types/prototyping/crypto/types/key-deriver.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/types/key-io.d.ts +49 -0
- package/dist/types/prototyping/crypto/types/key-io.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/types/key-manager.d.ts +69 -0
- package/dist/types/prototyping/crypto/types/key-manager.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/types/key-wrapper.d.ts +14 -0
- package/dist/types/prototyping/crypto/types/key-wrapper.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/types/params-direct.d.ts +75 -0
- package/dist/types/prototyping/crypto/types/params-direct.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/types/params-kms.d.ts +63 -0
- package/dist/types/prototyping/crypto/types/params-kms.d.ts.map +1 -0
- package/dist/types/prototyping/crypto/utils.d.ts +7 -0
- package/dist/types/prototyping/crypto/utils.d.ts.map +1 -0
- package/dist/types/prototyping/dids/resolver-cache-memory.d.ts +57 -0
- package/dist/types/prototyping/dids/resolver-cache-memory.d.ts.map +1 -0
- package/dist/types/prototyping/dids/utils.d.ts +3 -0
- package/dist/types/prototyping/dids/utils.d.ts.map +1 -0
- package/dist/types/rpc-client.d.ts +51 -0
- package/dist/types/rpc-client.d.ts.map +1 -0
- package/dist/types/store-data-protocols.d.ts +4 -0
- package/dist/types/store-data-protocols.d.ts.map +1 -0
- package/dist/types/store-data.d.ts +95 -0
- package/dist/types/store-data.d.ts.map +1 -0
- package/dist/types/store-did.d.ts +33 -0
- package/dist/types/store-did.d.ts.map +1 -0
- package/dist/types/store-identity.d.ts +34 -0
- package/dist/types/store-identity.d.ts.map +1 -0
- package/dist/types/store-key.d.ts +32 -0
- package/dist/types/store-key.d.ts.map +1 -0
- package/dist/types/sync-api.d.ts +41 -0
- package/dist/types/sync-api.d.ts.map +1 -0
- package/dist/types/sync-engine-level.d.ts +85 -0
- package/dist/types/sync-engine-level.d.ts.map +1 -0
- package/dist/types/test-harness.d.ts +69 -0
- package/dist/types/test-harness.d.ts.map +1 -0
- package/dist/types/types/agent.d.ts +172 -0
- package/dist/types/types/agent.d.ts.map +1 -0
- package/dist/types/types/dwn.d.ts +178 -0
- package/dist/types/types/dwn.d.ts.map +1 -0
- package/dist/types/types/identity-vault.d.ts +129 -0
- package/dist/types/types/identity-vault.d.ts.map +1 -0
- package/dist/types/types/identity.d.ts +16 -0
- package/dist/types/types/identity.d.ts.map +1 -0
- package/dist/types/types/key-manager.d.ts +9 -0
- package/dist/types/types/key-manager.d.ts.map +1 -0
- package/dist/types/types/permissions.d.ts +98 -0
- package/dist/types/types/permissions.d.ts.map +1 -0
- package/dist/types/types/sync.d.ts +66 -0
- package/dist/types/types/sync.d.ts.map +1 -0
- package/dist/types/types/vc.d.ts +7 -0
- package/dist/types/types/vc.d.ts.map +1 -0
- package/dist/types/utils-internal.d.ts +50 -0
- package/dist/types/utils-internal.d.ts.map +1 -0
- package/dist/types/utils.d.ts +37 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/package.json +112 -0
- package/src/agent-did-resolver-cache.ts +95 -0
- package/src/bearer-identity.ts +42 -0
- package/src/connect.ts +296 -0
- package/src/crypto-api.ts +593 -0
- package/src/did-api.ts +429 -0
- package/src/dwn-api.ts +462 -0
- package/src/dwn-registrar.ts +127 -0
- package/src/hd-identity-vault.ts +853 -0
- package/src/identity-api.ts +324 -0
- package/src/index.ts +30 -0
- package/src/local-key-manager.ts +672 -0
- package/src/oidc.ts +857 -0
- package/src/permissions-api.ts +408 -0
- package/src/prototyping/clients/dwn-rpc-types.ts +55 -0
- package/src/prototyping/clients/dwn-server-info-cache-memory.ts +79 -0
- package/src/prototyping/clients/http-dwn-rpc-client.ts +110 -0
- package/src/prototyping/clients/json-rpc-socket.ts +169 -0
- package/src/prototyping/clients/json-rpc.ts +113 -0
- package/src/prototyping/clients/server-info-types.ts +21 -0
- package/src/prototyping/clients/web-socket-clients.ts +100 -0
- package/src/prototyping/common/object.ts +15 -0
- package/src/prototyping/common/type-utils.ts +6 -0
- package/src/prototyping/crypto/algorithms/aes-gcm.ts +211 -0
- package/src/prototyping/crypto/algorithms/aes-kw.ts +164 -0
- package/src/prototyping/crypto/algorithms/ecdsa.ts +365 -0
- package/src/prototyping/crypto/algorithms/eddsa.ts +310 -0
- package/src/prototyping/crypto/algorithms/hkdf.ts +40 -0
- package/src/prototyping/crypto/algorithms/pbkdf2.ts +44 -0
- package/src/prototyping/crypto/crypto-error.ts +45 -0
- package/src/prototyping/crypto/dsa.ts +367 -0
- package/src/prototyping/crypto/jose/jwe-compact.ts +225 -0
- package/src/prototyping/crypto/jose/jwe-flattened.ts +459 -0
- package/src/prototyping/crypto/jose/jwe.ts +653 -0
- package/src/prototyping/crypto/primitives/aes-gcm.ts +374 -0
- package/src/prototyping/crypto/primitives/aes-kw.ts +271 -0
- package/src/prototyping/crypto/primitives/hkdf.ts +121 -0
- package/src/prototyping/crypto/primitives/pbkdf2.ts +116 -0
- package/src/prototyping/crypto/types/cipher.ts +17 -0
- package/src/prototyping/crypto/types/crypto-api.ts +78 -0
- package/src/prototyping/crypto/types/key-converter.ts +53 -0
- package/src/prototyping/crypto/types/key-deriver.ts +56 -0
- package/src/prototyping/crypto/types/key-io.ts +51 -0
- package/src/prototyping/crypto/types/key-manager.ts +83 -0
- package/src/prototyping/crypto/types/key-wrapper.ts +17 -0
- package/src/prototyping/crypto/types/params-direct.ts +95 -0
- package/src/prototyping/crypto/types/params-kms.ts +76 -0
- package/src/prototyping/crypto/utils.ts +41 -0
- package/src/prototyping/dids/resolver-cache-memory.ts +83 -0
- package/src/prototyping/dids/utils.ts +10 -0
- package/src/rpc-client.ts +162 -0
- package/src/store-data-protocols.ts +40 -0
- package/src/store-data.ts +400 -0
- package/src/store-did.ts +105 -0
- package/src/store-identity.ts +109 -0
- package/src/store-key.ts +104 -0
- package/src/sync-api.ts +71 -0
- package/src/sync-engine-level.ts +714 -0
- package/src/test-harness.ts +330 -0
- package/src/types/agent.ts +195 -0
- package/src/types/dwn.ts +278 -0
- package/src/types/identity-vault.ts +137 -0
- package/src/types/identity.ts +18 -0
- package/src/types/key-manager.ts +15 -0
- package/src/types/permissions.ts +115 -0
- package/src/types/sync.ts +58 -0
- package/src/types/vc.ts +7 -0
- package/src/utils-internal.ts +157 -0
- package/src/utils.ts +181 -0
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
import type { ULIDFactory } from 'ulidx';
|
|
2
|
+
import type { AbstractBatchOperation, AbstractLevel } from 'abstract-level';
|
|
3
|
+
import type {
|
|
4
|
+
GenericMessage,
|
|
5
|
+
MessagesQueryReply,
|
|
6
|
+
MessagesReadReply,
|
|
7
|
+
PaginationCursor,
|
|
8
|
+
UnionMessageReply,
|
|
9
|
+
} from '@enbox/dwn-sdk-js';
|
|
10
|
+
|
|
11
|
+
import ms from 'ms';
|
|
12
|
+
import { Level } from 'level';
|
|
13
|
+
import { monotonicFactory } from 'ulidx';
|
|
14
|
+
import { NodeStream } from '@enbox/common';
|
|
15
|
+
import {
|
|
16
|
+
DwnInterfaceName,
|
|
17
|
+
DwnMethodName,
|
|
18
|
+
} from '@enbox/dwn-sdk-js';
|
|
19
|
+
|
|
20
|
+
import type { SyncEngine, SyncIdentityOptions } from './types/sync.js';
|
|
21
|
+
import type { Web5Agent, Web5PlatformAgent } from './types/agent.js';
|
|
22
|
+
|
|
23
|
+
import { DwnInterface } from './types/dwn.js';
|
|
24
|
+
import { getDwnServiceEndpointUrls, isRecordsWrite } from './utils.js';
|
|
25
|
+
import { PermissionsApi } from './types/permissions.js';
|
|
26
|
+
import { AgentPermissionsApi } from './permissions-api.js';
|
|
27
|
+
|
|
28
|
+
export type SyncEngineLevelParams = {
|
|
29
|
+
agent?: Web5PlatformAgent;
|
|
30
|
+
dataPath?: string;
|
|
31
|
+
db?: AbstractLevel<string | Buffer | Uint8Array>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type LevelBatchOperation = AbstractBatchOperation<AbstractLevel<string | Buffer | Uint8Array>, string, string>;
|
|
35
|
+
|
|
36
|
+
type SyncDirection = 'push' | 'pull';
|
|
37
|
+
|
|
38
|
+
type SyncState = {
|
|
39
|
+
did: string;
|
|
40
|
+
delegateDid?: string;
|
|
41
|
+
dwnUrl: string;
|
|
42
|
+
cursor?: PaginationCursor,
|
|
43
|
+
protocol?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type SyncMessageParams = {
|
|
47
|
+
did: string;
|
|
48
|
+
messageCid: string;
|
|
49
|
+
watermark: string;
|
|
50
|
+
dwnUrl: string;
|
|
51
|
+
delegateDid?: string;
|
|
52
|
+
cursor?: PaginationCursor,
|
|
53
|
+
protocol?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class SyncEngineLevel implements SyncEngine {
|
|
57
|
+
/**
|
|
58
|
+
* Holds the instance of a `Web5PlatformAgent` that represents the current execution context for
|
|
59
|
+
* the `SyncEngineLevel`. This agent is used to interact with other Web5 agent components. It's
|
|
60
|
+
* vital to ensure this instance is set to correctly contextualize operations within the broader
|
|
61
|
+
* Web5 Agent framework.
|
|
62
|
+
*/
|
|
63
|
+
private _agent?: Web5PlatformAgent;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* An instance of the `AgentPermissionsApi` that is used to interact with permissions grants used during sync
|
|
67
|
+
*/
|
|
68
|
+
private _permissionsApi: PermissionsApi;;
|
|
69
|
+
|
|
70
|
+
private _db: AbstractLevel<string | Buffer | Uint8Array>;
|
|
71
|
+
private _syncIntervalId?: ReturnType<typeof setInterval>;
|
|
72
|
+
private _syncLock = false;
|
|
73
|
+
private _ulidFactory: ULIDFactory;
|
|
74
|
+
|
|
75
|
+
constructor({ agent, dataPath, db }: SyncEngineLevelParams) {
|
|
76
|
+
this._agent = agent;
|
|
77
|
+
this._permissionsApi = new AgentPermissionsApi({ agent: agent as Web5Agent });
|
|
78
|
+
this._db = (db) ? db : new Level<string, string>(dataPath ?? 'DATA/AGENT/SYNC_STORE');
|
|
79
|
+
this._ulidFactory = monotonicFactory();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Retrieves the `Web5PlatformAgent` execution context.
|
|
84
|
+
*
|
|
85
|
+
* @returns The `Web5PlatformAgent` instance that represents the current execution context.
|
|
86
|
+
* @throws Will throw an error if the `agent` instance property is undefined.
|
|
87
|
+
*/
|
|
88
|
+
get agent(): Web5PlatformAgent {
|
|
89
|
+
if (this._agent === undefined) {
|
|
90
|
+
throw new Error('SyncEngineLevel: Unable to determine agent execution context.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return this._agent;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
set agent(agent: Web5PlatformAgent) {
|
|
97
|
+
this._agent = agent;
|
|
98
|
+
this._permissionsApi = new AgentPermissionsApi({ agent: agent as Web5Agent });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public async clear(): Promise<void> {
|
|
102
|
+
await this._permissionsApi.clear();
|
|
103
|
+
await this._db.clear();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public async close(): Promise<void> {
|
|
107
|
+
await this._db.close();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async pull(): Promise<void> {
|
|
111
|
+
const syncPeerState = await this.getSyncPeerState({ syncDirection: 'pull' });
|
|
112
|
+
await this.enqueueOperations({ syncDirection: 'pull', syncPeerState });
|
|
113
|
+
|
|
114
|
+
const pullQueue = this.getPullQueue();
|
|
115
|
+
const pullJobs = await pullQueue.iterator().all();
|
|
116
|
+
|
|
117
|
+
const deleteOperations: LevelBatchOperation[] = [];
|
|
118
|
+
const errored: Set<string> = new Set();
|
|
119
|
+
|
|
120
|
+
for (let job of pullJobs) {
|
|
121
|
+
const [key] = job;
|
|
122
|
+
const { did, dwnUrl, messageCid, delegateDid, protocol } = SyncEngineLevel.parseSyncMessageParamsKey(key);
|
|
123
|
+
// If a particular DWN service endpoint is unreachable, skip subsequent pull operations.
|
|
124
|
+
if (errored.has(dwnUrl)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const messageExists = await this.messageExists(did, messageCid);
|
|
129
|
+
if (messageExists) {
|
|
130
|
+
deleteOperations.push({ type: 'del', key: key });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let permissionGrantId: string | undefined;
|
|
135
|
+
let granteeDid: string | undefined;
|
|
136
|
+
if (delegateDid) {
|
|
137
|
+
try {
|
|
138
|
+
const messagesReadGrant = await this._permissionsApi.getPermissionForRequest({
|
|
139
|
+
connectedDid : did,
|
|
140
|
+
messageType : DwnInterface.MessagesRead,
|
|
141
|
+
delegateDid,
|
|
142
|
+
protocol,
|
|
143
|
+
cached : true
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
permissionGrantId = messagesReadGrant.grant.id;
|
|
147
|
+
granteeDid = delegateDid;
|
|
148
|
+
} catch(error:any) {
|
|
149
|
+
console.error('SyncEngineLevel: pull - Error fetching MessagesRead permission grant for delegate DID', error);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const messagesRead = await this.agent.processDwnRequest({
|
|
155
|
+
store : false,
|
|
156
|
+
author : did,
|
|
157
|
+
target : did,
|
|
158
|
+
messageType : DwnInterface.MessagesRead,
|
|
159
|
+
granteeDid,
|
|
160
|
+
messageParams : {
|
|
161
|
+
messageCid,
|
|
162
|
+
permissionGrantId
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
let reply: MessagesReadReply;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
reply = await this.agent.rpc.sendDwnRequest({
|
|
170
|
+
dwnUrl, targetDid : did,
|
|
171
|
+
message : messagesRead.message,
|
|
172
|
+
}) as MessagesReadReply;
|
|
173
|
+
} catch(e) {
|
|
174
|
+
errored.add(dwnUrl);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (reply.status.code !== 200 || !reply.entry?.message) {
|
|
179
|
+
await this.addMessage(did, messageCid);
|
|
180
|
+
deleteOperations.push({ type: 'del', key: key });
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const replyEntry = reply.entry;
|
|
185
|
+
const message = replyEntry.message;
|
|
186
|
+
// if the message includes data we convert it to a Node readable stream
|
|
187
|
+
// otherwise we set it as undefined, as the message does not include data
|
|
188
|
+
// this occurs when the message is a RecordsWrite message that has been updated
|
|
189
|
+
const dataStream = isRecordsWrite(replyEntry) && replyEntry.data ?
|
|
190
|
+
NodeStream.fromWebReadable({ readableStream: replyEntry.data as unknown as ReadableStream })
|
|
191
|
+
: undefined;
|
|
192
|
+
|
|
193
|
+
const pullReply = await this.agent.dwn.node.processMessage(did, message, { dataStream: dataStream as any });
|
|
194
|
+
if (SyncEngineLevel.syncMessageReplyIsSuccessful(pullReply)) {
|
|
195
|
+
await this.addMessage(did, messageCid);
|
|
196
|
+
deleteOperations.push({ type: 'del', key: key });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await pullQueue.batch(deleteOperations as any);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async push(): Promise<void> {
|
|
204
|
+
const syncPeerState = await this.getSyncPeerState({ syncDirection: 'push' });
|
|
205
|
+
await this.enqueueOperations({ syncDirection: 'push', syncPeerState });
|
|
206
|
+
|
|
207
|
+
const pushQueue = this.getPushQueue();
|
|
208
|
+
const pushJobs = await pushQueue.iterator().all();
|
|
209
|
+
|
|
210
|
+
const deleteOperations: LevelBatchOperation[] = [];
|
|
211
|
+
const errored: Set<string> = new Set();
|
|
212
|
+
|
|
213
|
+
for (let job of pushJobs) {
|
|
214
|
+
const [key] = job;
|
|
215
|
+
const { did, delegateDid, protocol, dwnUrl, messageCid } = SyncEngineLevel.parseSyncMessageParamsKey(key);
|
|
216
|
+
// If a particular DWN service endpoint is unreachable, skip subsequent push operations.
|
|
217
|
+
if (errored.has(dwnUrl)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Attempt to retrieve the message from the local DWN.
|
|
222
|
+
const dwnMessage = await this.getDwnMessage({ author: did, messageCid, delegateDid, protocol });
|
|
223
|
+
|
|
224
|
+
// If the message does not exist on the local DWN, remove the sync operation from the
|
|
225
|
+
// push queue, update the push watermark for this DID/DWN endpoint combination, add the
|
|
226
|
+
// message to the local message store, and continue to the next job.
|
|
227
|
+
if (!dwnMessage) {
|
|
228
|
+
deleteOperations.push({ type: 'del', key: key });
|
|
229
|
+
await this.addMessage(did, messageCid);
|
|
230
|
+
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const reply = await this.agent.rpc.sendDwnRequest({
|
|
236
|
+
dwnUrl,
|
|
237
|
+
targetDid : did,
|
|
238
|
+
data : dwnMessage.data,
|
|
239
|
+
message : dwnMessage.message
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Update the watermark and add the messageCid to the Sync Message Store if either:
|
|
243
|
+
if (SyncEngineLevel.syncMessageReplyIsSuccessful(reply)) {
|
|
244
|
+
await this.addMessage(did, messageCid);
|
|
245
|
+
deleteOperations.push({ type: 'del', key: key });
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
248
|
+
// Error is intentionally ignored; 'errored' set is updated with 'dwnUrl'.
|
|
249
|
+
errored.add(dwnUrl);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await pushQueue.batch(deleteOperations as any);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public async registerIdentity({ did, options }: { did: string; options?: SyncIdentityOptions }): Promise<void> {
|
|
257
|
+
// Get a reference to the `registeredIdentities` sublevel.
|
|
258
|
+
const registeredIdentities = this._db.sublevel('registeredIdentities');
|
|
259
|
+
|
|
260
|
+
const existing = await this.getIdentityOptions(did);
|
|
261
|
+
if (existing) {
|
|
262
|
+
throw new Error(`SyncEngineLevel: Identity with DID ${did} is already registered.`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// if no options are provided, we default to no delegateDid and all protocols (empty array)
|
|
266
|
+
options ??= { protocols: [] };
|
|
267
|
+
|
|
268
|
+
// Add (or overwrite, if present) the Identity's DID as a registered identity.
|
|
269
|
+
await registeredIdentities.put(did, JSON.stringify(options));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public async unregisterIdentity(did: string): Promise<void> {
|
|
273
|
+
const registeredIdentities = this._db.sublevel('registeredIdentities');
|
|
274
|
+
const existing = await this.getIdentityOptions(did);
|
|
275
|
+
if (!existing) {
|
|
276
|
+
throw new Error(`SyncEngineLevel: Identity with DID ${did} is not registered.`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await registeredIdentities.del(did);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
public async getIdentityOptions(did: string): Promise<SyncIdentityOptions | undefined> {
|
|
283
|
+
const registeredIdentities = this._db.sublevel('registeredIdentities');
|
|
284
|
+
try {
|
|
285
|
+
const options = await registeredIdentities.get(did);
|
|
286
|
+
if (options) {
|
|
287
|
+
return JSON.parse(options) as SyncIdentityOptions;
|
|
288
|
+
}
|
|
289
|
+
} catch(error) {
|
|
290
|
+
const e = error as { code: string };
|
|
291
|
+
// `Level`` throws an error if the key is not present. Return `undefined` in this case.
|
|
292
|
+
if (e.code === 'LEVEL_NOT_FOUND') {
|
|
293
|
+
return;
|
|
294
|
+
} else {
|
|
295
|
+
throw new Error(`SyncEngineLevel: Error reading level: ${e.code}.`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public async updateIdentityOptions({ did, options }: { did: string, options: SyncIdentityOptions }): Promise<void> {
|
|
301
|
+
const registeredIdentities = this._db.sublevel('registeredIdentities');
|
|
302
|
+
const existingOptions = await this.getIdentityOptions(did);
|
|
303
|
+
if (!existingOptions) {
|
|
304
|
+
throw new Error(`SyncEngineLevel: Identity with DID ${did} is not registered.`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
await registeredIdentities.put(did, JSON.stringify(options));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
public async sync(direction?: 'push' | 'pull'): Promise<void> {
|
|
311
|
+
if (this._syncLock) {
|
|
312
|
+
throw new Error('SyncEngineLevel: Sync operation is already in progress.');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this._syncLock = true;
|
|
316
|
+
try {
|
|
317
|
+
if (!direction || direction === 'push') {
|
|
318
|
+
await this.push();
|
|
319
|
+
}
|
|
320
|
+
if (!direction || direction === 'pull') {
|
|
321
|
+
await this.pull();
|
|
322
|
+
}
|
|
323
|
+
} finally {
|
|
324
|
+
this._syncLock = false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
public async startSync({ interval }: {
|
|
329
|
+
interval: string
|
|
330
|
+
}): Promise<void> {
|
|
331
|
+
// Convert the interval string to milliseconds.
|
|
332
|
+
const intervalMilliseconds = ms(interval);
|
|
333
|
+
|
|
334
|
+
const intervalSync = async () => {
|
|
335
|
+
if (this._syncLock) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
clearInterval(this._syncIntervalId);
|
|
340
|
+
this._syncIntervalId = undefined;
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
await this.sync();
|
|
344
|
+
} catch (error) {
|
|
345
|
+
console.error('SyncEngineLevel: Error during sync operation', error);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!this._syncIntervalId) {
|
|
349
|
+
this._syncIntervalId = setInterval(intervalSync, intervalMilliseconds);
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
if (this._syncIntervalId) {
|
|
354
|
+
clearInterval(this._syncIntervalId);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Set up a new interval.
|
|
358
|
+
this._syncIntervalId = setInterval(intervalSync, intervalMilliseconds);
|
|
359
|
+
|
|
360
|
+
// initiate an immediate sync
|
|
361
|
+
if (!this._syncLock) {
|
|
362
|
+
await this.sync();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* stopSync currently awaits the completion of the current sync operation before stopping the sync interval.
|
|
368
|
+
* TODO: implement a signal to gracefully stop sync immediately https://github.com/TBD54566975/web5-js/issues/890
|
|
369
|
+
*/
|
|
370
|
+
public async stopSync(timeout: number = 2000): Promise<void> {
|
|
371
|
+
let elapsedTimeout = 0;
|
|
372
|
+
|
|
373
|
+
while(this._syncLock) {
|
|
374
|
+
if (elapsedTimeout >= timeout) {
|
|
375
|
+
throw new Error(`SyncEngineLevel: Existing sync operation did not complete within ${timeout} milliseconds.`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
elapsedTimeout += 100;
|
|
379
|
+
await new Promise((resolve) => setTimeout(resolve, timeout < 100 ? timeout : 100));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (this._syncIntervalId) {
|
|
383
|
+
clearInterval(this._syncIntervalId);
|
|
384
|
+
this._syncIntervalId = undefined;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* 202: message was successfully written to the remote DWN
|
|
390
|
+
* 204: an initial write message was written without any data, cannot yet be read until a subsequent message is written with data
|
|
391
|
+
* 409: message was already present on the remote DWN
|
|
392
|
+
* RecordsDelete and the status code is 404: the initial write message was not found or the message was already deleted
|
|
393
|
+
*/
|
|
394
|
+
private static syncMessageReplyIsSuccessful(reply: UnionMessageReply): boolean {
|
|
395
|
+
return reply.status.code === 202 ||
|
|
396
|
+
// a 204 status code is returned when the message was accepted without any data.
|
|
397
|
+
// This is the case for an initial RecordsWrite messages for records that have been updated.
|
|
398
|
+
// For context: https://github.com/TBD54566975/dwn-sdk-js/issues/695
|
|
399
|
+
reply.status.code === 204 ||
|
|
400
|
+
reply.status.code === 409 ||
|
|
401
|
+
(
|
|
402
|
+
// If the message is a RecordsDelete and the status code is 404, the initial write message was not found or the message was already deleted
|
|
403
|
+
reply.entry?.message.descriptor.interface === DwnInterfaceName.Records &&
|
|
404
|
+
reply.entry?.message.descriptor.method === DwnMethodName.Delete &&
|
|
405
|
+
reply.status.code === 404
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private async enqueueOperations({ syncDirection, syncPeerState }: {
|
|
410
|
+
syncDirection: SyncDirection,
|
|
411
|
+
syncPeerState: SyncState[]
|
|
412
|
+
}) {
|
|
413
|
+
const enqueueOps = await Promise.allSettled(syncPeerState.map(async (syncState) => {
|
|
414
|
+
// Get the event log from the remote DWN if pull sync, or local DWN if push sync.
|
|
415
|
+
const eventLog = await this.getDwnEventLog({
|
|
416
|
+
did : syncState.did,
|
|
417
|
+
delegateDid : syncState.delegateDid,
|
|
418
|
+
dwnUrl : syncState.dwnUrl,
|
|
419
|
+
cursor : syncState.cursor,
|
|
420
|
+
protocol : syncState.protocol,
|
|
421
|
+
syncDirection
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const syncOperations: LevelBatchOperation[] = [];
|
|
425
|
+
|
|
426
|
+
for (let messageCid of eventLog) {
|
|
427
|
+
const watermark = this._ulidFactory();
|
|
428
|
+
const operationKey = SyncEngineLevel.generateSyncMessageParamsKey({
|
|
429
|
+
...syncState,
|
|
430
|
+
watermark,
|
|
431
|
+
messageCid
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
syncOperations.push({ type: 'put', key: operationKey, value: '' });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (syncOperations.length > 0) {
|
|
438
|
+
const syncQueue = (syncDirection === 'pull')
|
|
439
|
+
? this.getPullQueue()
|
|
440
|
+
: this.getPushQueue();
|
|
441
|
+
await syncQueue.batch(syncOperations as any);
|
|
442
|
+
}
|
|
443
|
+
}));
|
|
444
|
+
|
|
445
|
+
// log any errors that occurred during the enqueuing process
|
|
446
|
+
enqueueOps.forEach((result, index) => {
|
|
447
|
+
if (result.status === 'rejected') {
|
|
448
|
+
const peerState = syncPeerState[index];
|
|
449
|
+
console.error(`SyncEngineLevel: Error enqueuing sync operation for peerState: ${JSON.stringify(peerState)}`, result.reason);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private static generateSyncMessageParamsKey({ did, delegateDid, dwnUrl, protocol, watermark, messageCid }:SyncMessageParams): string {
|
|
455
|
+
// Use "did~dwnUrl~watermark~messageCid" as the key in the sync queue.
|
|
456
|
+
// Note: It is critical that `watermark` precedes `messageCid` to ensure that when the sync
|
|
457
|
+
// jobs are pulled off the queue, they are lexographically sorted oldest to newest.
|
|
458
|
+
//
|
|
459
|
+
// `protocol` and `delegateDid` may be undefined, which is fine, its part of the key will be stored as an empty string.
|
|
460
|
+
// Later, when parsing the key, we will handle this case and return an actual undefined.
|
|
461
|
+
// This is information useful for subset and delegated sync.
|
|
462
|
+
return [did, delegateDid, dwnUrl, protocol, watermark, messageCid ].join('~');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private static parseSyncMessageParamsKey(key: string): SyncMessageParams {
|
|
466
|
+
// The order is import here, see `generateKey` for more information.
|
|
467
|
+
const [did, delegateDidString, dwnUrl, protocolString, watermark, messageCid] = key.split('~');
|
|
468
|
+
|
|
469
|
+
// `protocol` or `delegateDid` may be parsed as an empty string, so we need to handle that case and returned an actual undefined.
|
|
470
|
+
const protocol = protocolString === '' ? undefined : protocolString;
|
|
471
|
+
const delegateDid = delegateDidString === '' ? undefined : delegateDidString;
|
|
472
|
+
return { did, delegateDid, dwnUrl, watermark, messageCid, protocol };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private async getDwnEventLog({ did, delegateDid, dwnUrl, syncDirection, cursor, protocol }: {
|
|
476
|
+
did: string,
|
|
477
|
+
delegateDid?: string,
|
|
478
|
+
dwnUrl: string,
|
|
479
|
+
syncDirection: SyncDirection,
|
|
480
|
+
cursor?: PaginationCursor
|
|
481
|
+
protocol?: string
|
|
482
|
+
}) {
|
|
483
|
+
let messagesReply = {} as MessagesQueryReply;
|
|
484
|
+
let permissionGrantId: string | undefined;
|
|
485
|
+
if (delegateDid) {
|
|
486
|
+
// fetch the grants for the delegate DID
|
|
487
|
+
try {
|
|
488
|
+
const messagesQueryGrant = await this._permissionsApi.getPermissionForRequest({
|
|
489
|
+
connectedDid : did,
|
|
490
|
+
messageType : DwnInterface.MessagesQuery,
|
|
491
|
+
delegateDid,
|
|
492
|
+
protocol,
|
|
493
|
+
cached : true
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
permissionGrantId = messagesQueryGrant.grant.id;
|
|
497
|
+
} catch(error:any) {
|
|
498
|
+
console.error('SyncEngineLevel: Error fetching MessagesQuery permission grant for delegate DID', error);
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (syncDirection === 'pull') {
|
|
504
|
+
// filter for a specific protocol if one is provided
|
|
505
|
+
const filters = protocol ? [{ protocol }] : [];
|
|
506
|
+
// When sync is a pull, get the event log from the remote DWN.
|
|
507
|
+
const messagesQueryMessage = await this.agent.dwn.processRequest({
|
|
508
|
+
store : false,
|
|
509
|
+
target : did,
|
|
510
|
+
author : did,
|
|
511
|
+
messageType : DwnInterface.MessagesQuery,
|
|
512
|
+
granteeDid : delegateDid,
|
|
513
|
+
messageParams : { filters, cursor, permissionGrantId }
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
try {
|
|
517
|
+
messagesReply = await this.agent.rpc.sendDwnRequest({
|
|
518
|
+
dwnUrl : dwnUrl,
|
|
519
|
+
targetDid : did,
|
|
520
|
+
message : messagesQueryMessage.message,
|
|
521
|
+
}) as MessagesQueryReply;
|
|
522
|
+
} catch {
|
|
523
|
+
// If a particular DWN service endpoint is unreachable, silently ignore.
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
} else if (syncDirection === 'push') {
|
|
527
|
+
const filters = protocol ? [{ protocol }] : [];
|
|
528
|
+
// When sync is a push, get the event log from the local DWN.
|
|
529
|
+
const messagesQueryDwnResponse = await this.agent.dwn.processRequest({
|
|
530
|
+
author : did,
|
|
531
|
+
target : did,
|
|
532
|
+
messageType : DwnInterface.MessagesQuery,
|
|
533
|
+
granteeDid : delegateDid,
|
|
534
|
+
messageParams : { filters, cursor, permissionGrantId }
|
|
535
|
+
});
|
|
536
|
+
messagesReply = messagesQueryDwnResponse.reply as MessagesQueryReply;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const eventLog = messagesReply.entries ?? [];
|
|
540
|
+
if (messagesReply.cursor) {
|
|
541
|
+
this.setCursor(did, dwnUrl, syncDirection, messagesReply.cursor, protocol);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return eventLog;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private async getDwnMessage({ author, delegateDid, protocol, messageCid }: {
|
|
548
|
+
author: string;
|
|
549
|
+
delegateDid?: string;
|
|
550
|
+
protocol?: string;
|
|
551
|
+
messageCid: string;
|
|
552
|
+
}): Promise<{ message: GenericMessage, data?: Blob } | undefined> {
|
|
553
|
+
let permissionGrantId: string | undefined;
|
|
554
|
+
if (delegateDid) {
|
|
555
|
+
try {
|
|
556
|
+
const messagesReadGrant = await this._permissionsApi.getPermissionForRequest({
|
|
557
|
+
connectedDid : author,
|
|
558
|
+
messageType : DwnInterface.MessagesRead,
|
|
559
|
+
delegateDid,
|
|
560
|
+
protocol,
|
|
561
|
+
cached : true
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
permissionGrantId = messagesReadGrant.grant.id;
|
|
565
|
+
} catch(error:any) {
|
|
566
|
+
console.error('SyncEngineLevel: push - Error fetching MessagesRead permission grant for delegate DID', error);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
let { reply } = await this.agent.dwn.processRequest({
|
|
572
|
+
author : author,
|
|
573
|
+
target : author,
|
|
574
|
+
messageType : DwnInterface.MessagesRead,
|
|
575
|
+
granteeDid : delegateDid,
|
|
576
|
+
messageParams : { messageCid, permissionGrantId }
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
// Absence of a messageEntry or message within messageEntry can happen because updating a
|
|
581
|
+
// Record creates another RecordsWrite with the same recordId. Only the first and
|
|
582
|
+
// most recent RecordsWrite messages are kept for a given recordId. Any RecordsWrite messages
|
|
583
|
+
// that aren't the first or most recent are discarded by the DWN.
|
|
584
|
+
if (reply.status.code !== 200 || !reply.entry) {
|
|
585
|
+
return undefined;
|
|
586
|
+
}
|
|
587
|
+
const messageEntry = reply.entry!;
|
|
588
|
+
|
|
589
|
+
let dwnMessageWithBlob: { message: GenericMessage, data?: Blob } = { message: messageEntry.message };
|
|
590
|
+
|
|
591
|
+
// If the message is a RecordsWrite, either data will be present,
|
|
592
|
+
// OR we have to fetch it using a RecordsRead.
|
|
593
|
+
if (isRecordsWrite(messageEntry) && messageEntry.data) {
|
|
594
|
+
const dataBytes = await NodeStream.consumeToBytes({ readable: messageEntry.data });
|
|
595
|
+
dwnMessageWithBlob.data = new Blob([ dataBytes ], { type: messageEntry.message.descriptor.dataFormat });
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return dwnMessageWithBlob;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
private async getSyncPeerState({ syncDirection }: {
|
|
602
|
+
syncDirection: SyncDirection;
|
|
603
|
+
}): Promise<SyncState[]> {
|
|
604
|
+
|
|
605
|
+
// Array to accumulate the list of sync peers for each DID.
|
|
606
|
+
const syncPeerState: SyncState[] = [];
|
|
607
|
+
|
|
608
|
+
// iterate over all registered identities
|
|
609
|
+
for await (const [ did, options ] of this._db.sublevel('registeredIdentities').iterator()) {
|
|
610
|
+
|
|
611
|
+
const { protocols, delegateDid } = await new Promise<SyncIdentityOptions>((resolve) => {
|
|
612
|
+
try {
|
|
613
|
+
const { protocols, delegateDid } = JSON.parse(options) as SyncIdentityOptions;
|
|
614
|
+
resolve({ protocols, delegateDid });
|
|
615
|
+
} catch(error: any) {
|
|
616
|
+
resolve({ protocols: [] });
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// First, confirm the DID can be resolved and extract the DWN service endpoint URLs.
|
|
621
|
+
const dwnEndpointUrls = await getDwnServiceEndpointUrls(did, this.agent.did);
|
|
622
|
+
if (dwnEndpointUrls.length === 0) {
|
|
623
|
+
// Silently ignore and do not try to perform Sync for any DID that does not have a DWN
|
|
624
|
+
// service endpoint published in its DID document.
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Get the cursor (or undefined) for each (DID, DWN service endpoint, sync direction)
|
|
629
|
+
// combination and add it to the sync peer state array.
|
|
630
|
+
for (let dwnUrl of dwnEndpointUrls) {
|
|
631
|
+
if (protocols.length === 0) {
|
|
632
|
+
const cursor = await this.getCursor(did, dwnUrl, syncDirection);
|
|
633
|
+
syncPeerState.push({ did, delegateDid, dwnUrl, cursor });
|
|
634
|
+
} else {
|
|
635
|
+
for (const protocol of protocols) {
|
|
636
|
+
const cursor = await this.getCursor(did, dwnUrl, syncDirection, protocol);
|
|
637
|
+
syncPeerState.push({ did, delegateDid, dwnUrl, cursor, protocol });
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return syncPeerState;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private async getCursor(did: string, dwnUrl: string, direction: SyncDirection, protocol?: string): Promise<PaginationCursor | undefined> {
|
|
647
|
+
|
|
648
|
+
// if a protocol is provided, we append it to the key
|
|
649
|
+
const cursorKey = protocol ? `${did}~${dwnUrl}~${direction}-${protocol}` :
|
|
650
|
+
`${did}~${dwnUrl}~${direction}`;
|
|
651
|
+
|
|
652
|
+
const cursorsStore = this.getCursorStore();
|
|
653
|
+
try {
|
|
654
|
+
const cursorValue = await cursorsStore.get(cursorKey);
|
|
655
|
+
if (cursorValue) {
|
|
656
|
+
return JSON.parse(cursorValue) as PaginationCursor;
|
|
657
|
+
}
|
|
658
|
+
} catch(error: any) {
|
|
659
|
+
// Don't throw when a key wasn't found.
|
|
660
|
+
if (error.notFound) {
|
|
661
|
+
return undefined;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
private async setCursor(did: string, dwnUrl: string, direction: SyncDirection, cursor: PaginationCursor, protocol?: string) {
|
|
667
|
+
const cursorKey = protocol ? `${did}~${dwnUrl}~${direction}-${protocol}` :
|
|
668
|
+
`${did}~${dwnUrl}~${direction}`;
|
|
669
|
+
const cursorsStore = this.getCursorStore();
|
|
670
|
+
await cursorsStore.put(cursorKey, JSON.stringify(cursor));
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* The message store is used to prevent "echoes" that occur during a sync pull operation.
|
|
675
|
+
* After a message is confirmed to already be synchronized on the local DWN, its CID is added
|
|
676
|
+
* to the message store to ensure that any subsequent pull attempts are skipped.
|
|
677
|
+
*/
|
|
678
|
+
private async messageExists(did: string, messageCid: string) {
|
|
679
|
+
const messageStore = this.getMessageStore(did);
|
|
680
|
+
|
|
681
|
+
// If the `messageCid` exists in this DID's store, return true. Otherwise, return false.
|
|
682
|
+
try {
|
|
683
|
+
await messageStore.get(messageCid);
|
|
684
|
+
return true;
|
|
685
|
+
} catch (error: any) {
|
|
686
|
+
if (error.notFound) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
throw error;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
private async addMessage(did: string, messageCid: string) {
|
|
694
|
+
const messageStore = this.getMessageStore(did);
|
|
695
|
+
|
|
696
|
+
return await messageStore.put(messageCid, '');
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
private getMessageStore(did: string) {
|
|
700
|
+
return this._db.sublevel('history').sublevel(did).sublevel('messages');
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
private getCursorStore() {
|
|
704
|
+
return this._db.sublevel('cursors');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
private getPushQueue() {
|
|
708
|
+
return this._db.sublevel('pushQueue');
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
private getPullQueue() {
|
|
712
|
+
return this._db.sublevel('pullQueue');
|
|
713
|
+
}
|
|
714
|
+
}
|