-
Notifications
You must be signed in to change notification settings - Fork 8
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
Integrate @web5/identity-agent to create identity on app launch #62
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { getArrayBufferForBlob } from "react-native-blob-jsi-helper"; | ||
import { ReadableStream } from "web-streams-polyfill"; | ||
|
||
const decoder = new TextDecoder(); | ||
|
||
/** | ||
* React Native's Blob implementation has a constructor that currently only supports | ||
* constructing from parts that are of type: Array<Blob | String>. | ||
Comment on lines
+7
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. React Native's Blob can be found at |
||
* | ||
* Web5 packages need to be able to construct a Blob where one of the parts is of type `Uint8Array`. | ||
* | ||
* This function updates the constructor of Blob to convert any parts that are of type `Uint8Array` | ||
* into a `string`, so that it can properly work with React Native's Blob implementation. | ||
*/ | ||
function monkeyPatchBlobConstructor() { | ||
const OriginalBlob = global.Blob; | ||
|
||
const blobProxyHandler = { | ||
construct(target: any, argumentsList: any) { | ||
const blobParts = argumentsList[0]; | ||
const options = argumentsList[1]; | ||
|
||
if (blobParts) { | ||
for (const [index, element] of blobParts.entries()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how many times are we potentially looping here? should we do an upstream PR to fix this inside of react native itself? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is, potentially, very expensive code. if we are using blobs elsewhere we risk torpedoing the app's performance. i wonder what an upstream solution would involve? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should probably slap a TODO here and we should create an issue that tracks the TODO. We'll want visibility into the performance impact and it would be a good issue that could be contributed upstream by either OSS stakeholders or our own teams There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if (element instanceof Uint8Array) { | ||
blobParts[index] = decoder.decode(element); | ||
} | ||
} | ||
} | ||
|
||
return new target(blobParts, options); | ||
}, | ||
}; | ||
|
||
const blobProxy = new Proxy(OriginalBlob, blobProxyHandler); | ||
(global as any).Blob = blobProxy; | ||
amika-sq marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
* React Native's Blob implementation currently does provide a `stream()` function. | ||
* | ||
* Web5 packages depend on this function for a multitude of functionality. | ||
* | ||
* This function provides a polyfill for this function if it does not exist. | ||
*/ | ||
function polyfillBlobStream() { | ||
if (typeof Blob !== "undefined") { | ||
if (!Blob.prototype.stream) { | ||
Blob.prototype.stream = function (): ReadableStream<Uint8Array> { | ||
const blob = this; | ||
|
||
return new ReadableStream({ | ||
async start(controller) { | ||
const arrayBuffer = await getArrayBuffer(blob); | ||
controller.enqueue(arrayBuffer); | ||
controller.close(); | ||
}, | ||
}); | ||
}; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Web5 will often create a Blob, then immediately use the Blob's `stream()` | ||
* to stream the data for various purposes. | ||
* | ||
* In React Native, Blobs are created via NativeBlobModule. Despite the API to | ||
* create Blobs being synchronous, the actual bytes backing the Blob may not be | ||
* available immmediately. | ||
* | ||
* This function looks for the backing arrayBuffer for a Blob, with small microsleeps | ||
* until it is ultimately available via the JSI. | ||
*/ | ||
async function getArrayBuffer(blob: Blob): Promise<Uint8Array> { | ||
let arrayBuffer: Uint8Array | undefined = undefined; | ||
try { | ||
arrayBuffer = getArrayBufferForBlob(blob); | ||
} catch {} | ||
|
||
if (arrayBuffer && arrayBuffer.length === blob.size) { | ||
return arrayBuffer; | ||
} else { | ||
// The buffer isn't available yet from the JSI. | ||
// Microsleep to advance to the next runloop, and try again. | ||
await sleep(1); | ||
return getArrayBuffer(blob); | ||
} | ||
} | ||
|
||
// eslint-disable-next-line require-await | ||
amika-sq marked this conversation as resolved.
Show resolved
Hide resolved
|
||
async function sleep(ms: number) { | ||
return new Promise((resolve) => setTimeout(resolve, ms)); | ||
} | ||
|
||
export function polyfillBlob() { | ||
monkeyPatchBlobConstructor(); | ||
polyfillBlobStream(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
diff --git a/node_modules/@web5/agent/dist/esm/app-data-store.js b/node_modules/@web5/agent/dist/esm/app-data-store.js | ||
index e8e64f0..e418b51 100644 | ||
--- a/node_modules/@web5/agent/dist/esm/app-data-store.js | ||
+++ b/node_modules/@web5/agent/dist/esm/app-data-store.js | ||
@@ -11,7 +11,7 @@ import { DidKeyMethod } from '@web5/dids'; | ||
import { hkdf } from '@noble/hashes/hkdf'; | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
import { sha512 } from '@noble/hashes/sha512'; | ||
-import { randomBytes } from '@web5/crypto/utils'; | ||
+import { randomBytes } from '@web5/crypto/dist/esm/utils'; | ||
import { pbkdf2Async } from '@noble/hashes/pbkdf2'; | ||
import { Convert, MemoryStore } from '@web5/common'; | ||
import { CryptoKey, Jose, XChaCha20Poly1305 } from '@web5/crypto'; | ||
diff --git a/node_modules/@web5/agent/dist/esm/dwn-manager.js b/node_modules/@web5/agent/dist/esm/dwn-manager.js | ||
index 3b68855..0c80d18 100644 | ||
--- a/node_modules/@web5/agent/dist/esm/dwn-manager.js | ||
+++ b/node_modules/@web5/agent/dist/esm/dwn-manager.js | ||
@@ -8,9 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge | ||
}); | ||
}; | ||
import { Jose } from '@web5/crypto'; | ||
-import * as didUtils from '@web5/dids/utils'; | ||
+import * as didUtils from '@web5/dids/dist/esm/utils'; | ||
import { Convert, removeUndefinedProperties } from '@web5/common'; | ||
-import { EventLogLevel, DataStoreLevel, MessageStoreLevel, } from '@tbd54566975/dwn-sdk-js/stores'; | ||
+import { EventLogLevel, DataStoreLevel, MessageStoreLevel, } from '@tbd54566975/dwn-sdk-js/dist/esm/src/index-stores'; | ||
import { Cid, Dwn, Message, EventsGet, DataStream, RecordsRead, MessagesGet, RecordsWrite, RecordsQuery, DwnMethodName, RecordsDelete, ProtocolsQuery, DwnInterfaceName, ProtocolsConfigure, } from '@tbd54566975/dwn-sdk-js'; | ||
import { isManagedKeyPair } from './utils.js'; | ||
import { blobToIsomorphicNodeReadable, webReadableToIsomorphicNodeReadable } from './utils.js'; | ||
diff --git a/node_modules/@web5/agent/dist/esm/kms-local.js b/node_modules/@web5/agent/dist/esm/kms-local.js | ||
index 561cc9c..4475f51 100644 | ||
--- a/node_modules/@web5/agent/dist/esm/kms-local.js | ||
+++ b/node_modules/@web5/agent/dist/esm/kms-local.js | ||
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
-import { isCryptoKeyPair, checkRequiredProperty } from '@web5/crypto/utils'; | ||
+import { isCryptoKeyPair, checkRequiredProperty } from '@web5/crypto/dist/esm/utils'; | ||
import { EcdhAlgorithm, EcdsaAlgorithm, EdDsaAlgorithm, AesCtrAlgorithm, } from '@web5/crypto'; | ||
import { isManagedKey, isManagedKeyPair } from './utils.js'; | ||
import { KeyStoreMemory, PrivateKeyStoreMemory } from './store-managed-key.js'; | ||
diff --git a/node_modules/@web5/agent/dist/esm/rpc-client.js b/node_modules/@web5/agent/dist/esm/rpc-client.js | ||
index 020df4e..c768329 100644 | ||
--- a/node_modules/@web5/agent/dist/esm/rpc-client.js | ||
+++ b/node_modules/@web5/agent/dist/esm/rpc-client.js | ||
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
-import { randomUuid } from '@web5/crypto/utils'; | ||
+import { randomUuid } from '@web5/crypto/dist/esm/utils'; | ||
import { createJsonRpcRequest, parseJson } from './json-rpc.js'; | ||
/** | ||
* Client used to communicate with Dwn Servers | ||
diff --git a/node_modules/@web5/agent/dist/esm/store-managed-key.js b/node_modules/@web5/agent/dist/esm/store-managed-key.js | ||
index ab4cb58..1a42192 100644 | ||
--- a/node_modules/@web5/agent/dist/esm/store-managed-key.js | ||
+++ b/node_modules/@web5/agent/dist/esm/store-managed-key.js | ||
@@ -18,7 +18,7 @@ var __rest = (this && this.__rest) || function (s, e) { | ||
} | ||
return t; | ||
}; | ||
-import { randomUuid } from '@web5/crypto/utils'; | ||
+import { randomUuid } from '@web5/crypto/dist/esm/utils'; | ||
import { Convert, removeEmptyObjects, removeUndefinedProperties } from '@web5/common'; | ||
import { isManagedKeyPair } from './utils.js'; | ||
/** | ||
diff --git a/node_modules/@web5/agent/dist/esm/test-managed-agent.js b/node_modules/@web5/agent/dist/esm/test-managed-agent.js | ||
index 2835aa0..cec93df 100644 | ||
--- a/node_modules/@web5/agent/dist/esm/test-managed-agent.js | ||
+++ b/node_modules/@web5/agent/dist/esm/test-managed-agent.js | ||
@@ -11,7 +11,7 @@ import { Jose } from '@web5/crypto'; | ||
import { Dwn } from '@tbd54566975/dwn-sdk-js'; | ||
import { LevelStore, MemoryStore } from '@web5/common'; | ||
import { DidIonMethod, DidKeyMethod, DidResolver, DidResolverCacheLevel } from '@web5/dids'; | ||
-import { MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@tbd54566975/dwn-sdk-js/stores'; | ||
+import { MessageStoreLevel, DataStoreLevel, EventLogLevel } from '@tbd54566975/dwn-sdk-js/dist/esm/src/index-stores'; | ||
import { LocalKms } from './kms-local.js'; | ||
import { DidManager } from './did-manager.js'; | ||
import { DwnManager } from './dwn-manager.js'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
diff --git a/node_modules/@web5/dids/dist/esm/did-key.js b/node_modules/@web5/dids/dist/esm/did-key.js | ||
index 420f8ab..9a0c961 100644 | ||
--- a/node_modules/@web5/dids/dist/esm/did-key.js | ||
+++ b/node_modules/@web5/dids/dist/esm/did-key.js | ||
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge | ||
}); | ||
}; | ||
import { universalTypeOf } from '@web5/common'; | ||
-import { keyToMultibaseId, multibaseIdToKey } from '@web5/crypto/utils'; | ||
+import { keyToMultibaseId, multibaseIdToKey } from '@web5/crypto/dist/esm/utils'; | ||
import { Jose, Ed25519, Secp256k1, EcdsaAlgorithm, EdDsaAlgorithm, } from '@web5/crypto'; | ||
import { getVerificationMethodTypes, parseDid } from './utils.js'; | ||
const SupportedCryptoAlgorithms = [ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
diff --git a/node_modules/react-native-blob-jsi-helper/android/src/main/cpp/cpp-adapter.cpp b/node_modules/react-native-blob-jsi-helper/android/src/main/cpp/cpp-adapter.cpp | ||
index 1f05fe2..faf7534 100644 | ||
--- a/node_modules/react-native-blob-jsi-helper/android/src/main/cpp/cpp-adapter.cpp | ||
+++ b/node_modules/react-native-blob-jsi-helper/android/src/main/cpp/cpp-adapter.cpp | ||
@@ -118,6 +118,11 @@ Java_com_reactnativeblobjsihelper_BlobJsiHelperModule_nativeInstall(JNIEnv *env, | ||
offset, | ||
size); | ||
env->DeleteLocalRef(jstring); | ||
+ if (env->ExceptionCheck()) { | ||
+ env->ExceptionDescribe(); | ||
+ env->ExceptionClear(); | ||
+ throw std::runtime_error("Error calling getBufferJava"); | ||
+ } | ||
|
||
jboolean isCopy = true; | ||
jbyte* bytes = env->GetByteArrayElements(boxedBytes, &isCopy); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import type { KeyValueStore } from "@web5/common"; | ||
import { ExpoLevel } from "expo-level"; | ||
|
||
export class ExpoLevelStore implements KeyValueStore<string, any> { | ||
private store: ExpoLevel<string, string>; | ||
|
||
constructor(location = "DATASTORE") { | ||
this.store = new ExpoLevel(location); | ||
} | ||
|
||
async clear(): Promise<void> { | ||
await this.store.clear(); | ||
} | ||
|
||
async close(): Promise<void> { | ||
await this.store.close(); | ||
} | ||
|
||
async delete(key: string): Promise<boolean> { | ||
await this.store.del(key); | ||
return true; | ||
} | ||
|
||
async get(key: string): Promise<any> { | ||
return await this.store.get(key); | ||
} | ||
|
||
async set(key: string, value: any): Promise<void> { | ||
await this.store.put(key, value); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { IdentityAgent } from "@web5/identity-agent"; | ||
import { AppDataVault, DwnManager } from "@web5/agent"; | ||
import { ExpoLevelStore } from "./expo-level-store"; | ||
import { Dwn } from "@tbd54566975/dwn-sdk-js"; | ||
|
||
export async function bootstrapIdentityAgent( | ||
passphrase: string, | ||
name: string, | ||
dwn: Dwn | ||
) { | ||
const dwnManager = new DwnManager({ dwn }); | ||
const appData = new AppDataVault({ | ||
keyDerivationWorkFactor: 1, | ||
store: new ExpoLevelStore("AppDataVault"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this a stopgap until we create a securestore for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @frankhinek is there any plans here? afaik, this is the solution that was come up with, no idea about secure stores yet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess my question is are we supposed to handle encryption using the device's native libraries (e.g. using keychain) or is web5 internally handling encryption for us? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to @frankhinek's document, that encryption is already done before being put into the store: https://hackmd.io/ShKU_Q9aQC6C96v7SaXl3A?view#First-Launch @frankhinek: lmk if that's not correct, and we need to do some additional encryption on top! |
||
}); | ||
|
||
console.log("Creating IdentityAgent..."); | ||
const agent = await IdentityAgent.create({ dwnManager, appData }); | ||
console.log("Starting IdentityAgent..."); | ||
await agent.start({ passphrase }); | ||
|
||
console.log(`Creating ${name} ManagedIdentity...`); | ||
const identity = await agent.identityManager.create({ | ||
name, | ||
didMethod: "ion", | ||
kms: "local", | ||
}); | ||
console.log(`Created ${identity.name} ManagedIdentity: ${identity.did}`); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have https://github.com/browserify/stream-browserify and readable-stream. do we need to add this or can we make do with the stream polyfills we already have?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially had tried that, but it's incompatible with this part of Web5 https://github.com/TBD54566975/web5-js/blob/v0.7.11/packages/web5-user-agent/src/utils.ts#L8