diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 989091a89..820881c4b 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -57,6 +57,7 @@ import org.xmtp.android.library.push.XMTPPush import org.xmtp.android.library.toHex import org.xmtp.proto.keystore.api.v1.Keystore.TopicMap.TopicData import org.xmtp.proto.message.api.v1.MessageApiOuterClass +import org.xmtp.proto.message.contents.Invitation.ConsentProofPayload import org.xmtp.proto.message.contents.PrivateKeyOuterClass import uniffi.xmtpv3.GroupPermissions import java.io.File @@ -712,11 +713,25 @@ class XMTPModule : Module() { } } - AsyncFunction("createConversation") Coroutine { clientAddress: String, peerAddress: String, contextJson: String -> + AsyncFunction("createConversation") Coroutine { clientAddress: String, peerAddress: String, contextJson: String, consentProofPayload: List -> withContext(Dispatchers.IO) { logV("createConversation: $contextJson") val client = clients[clientAddress] ?: throw XMTPException("No client") val context = JsonParser.parseString(contextJson).asJsonObject + + var consentProof: ConsentProofPayload? = null + if (consentProofPayload.isNotEmpty()) { + val consentProofDataBytes = consentProofPayload.foldIndexed(ByteArray(consentProofPayload.size)) { i, a, v -> + a.apply { + set( + i, + v.toByte() + ) + } + } + consentProof = ConsentProofPayload.parseFrom(consentProofDataBytes) + } + val conversation = client.conversations.newConversation( peerAddress, context = InvitationV1ContextBuilder.buildFromConversation( @@ -733,11 +748,15 @@ class XMTPModule : Module() { else -> mapOf() }, - ) + ), + consentProof ) if (conversation.keyMaterial == null) { logV("Null key material before encode conversation") } + if (conversation.consentProof == null) { + logV("Null consent before encode conversation") + } ConversationWrapper.encode(client, conversation) } } diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt index efb0e3a01..09854f46d 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt @@ -8,7 +8,7 @@ import org.xmtp.android.library.Conversation class ConversationWrapper { companion object { - fun encodeToObj(client: Client, conversation: Conversation): Map { + fun encodeToObj(client: Client, conversation: Conversation): Map { val context = when (conversation.version) { Conversation.Version.V2 -> mapOf( "conversationID" to (conversation.conversationId ?: ""), @@ -26,7 +26,8 @@ class ConversationWrapper { "peerAddress" to conversation.peerAddress, "version" to "DIRECT", "conversationID" to (conversation.conversationId ?: ""), - "keyMaterial" to (conversation.keyMaterial?.let { Base64.encodeToString(it, Base64.NO_WRAP) } ?: "") + "keyMaterial" to (conversation.keyMaterial?.let { Base64.encodeToString(it, Base64.NO_WRAP) } ?: ""), + "consentProof" to if (conversation.consentProof != null) Base64.encodeToString(conversation.consentProof?.toByteArray(), Base64.NO_WRAP) else null ) } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index c5244f04d..041398a4f 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -769,4 +769,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2 -COCOAPODS: 1.14.2 +COCOAPODS: 1.15.2 diff --git a/example/package.json b/example/package.json index 3391a849e..5fac9e8f2 100644 --- a/example/package.json +++ b/example/package.json @@ -15,6 +15,7 @@ "@thirdweb-dev/react-native": "^0.6.2", "@thirdweb-dev/react-native-compat": "^0.6.2", "@xmtp/frames-client": "^0.3.2", + "@xmtp/proto": "3.54.0", "ethers": "^5.7.2", "expo": "~48.0.18", "expo-document-picker": "^11.5.4", diff --git a/example/src/tests/tests.ts b/example/src/tests/tests.ts index fe5f1668c..388edd8fd 100644 --- a/example/src/tests/tests.ts +++ b/example/src/tests/tests.ts @@ -1,6 +1,7 @@ import { FramesClient } from '@xmtp/frames-client' -import { content, keystore } from '@xmtp/proto' +import { content, invitation } from '@xmtp/proto' import { createHmac } from 'crypto' +import { ethers } from 'ethers' import ReactNativeBlobUtil from 'react-native-blob-util' import Config from 'react-native-config' import { TextEncoder, TextDecoder } from 'text-encoding' @@ -1790,3 +1791,61 @@ test('can handle complex streaming setup with messages from self', async () => { return true }) + +test('can send and receive consent proofs', async () => { + const alixWallet = await ethers.Wallet.createRandom() + const boWallet = await ethers.Wallet.createRandom() + const bo = await Client.create(boWallet, { env: 'local' }) + await delayToPropogate() + const alix = await Client.create(alixWallet, { env: 'local' }) + await delayToPropogate() + + const timestamp = Date.now() + const consentMessage = + 'XMTP : Grant inbox consent to sender\n' + + '\n' + + `Current Time: ${new Date(timestamp).toUTCString()}\n` + + `From Address: ${bo.address}\n` + + '\n' + + 'For more info: https://xmtp.org/signatures/' + const sig = await alixWallet.signMessage(consentMessage) + const consentProof = invitation.ConsentProofPayload.fromPartial({ + payloadVersion: + invitation.ConsentProofPayloadVersion.CONSENT_PROOF_PAYLOAD_VERSION_1, + signature: sig, + timestamp, + }) + + const boConvo = await bo.conversations.newConversation( + alix.address, + undefined, + consentProof + ) + await delayToPropogate() + assert(!!boConvo?.consentProof, 'bo consentProof should exist') + const convos = await alix.conversations.list() + const alixConvo = convos.find((convo) => convo.topic === boConvo.topic) + await delayToPropogate() + assert(!!alixConvo?.consentProof, ' alix consentProof should not exist') + await delayToPropogate() + await alix.contacts.refreshConsentList() + const isAllowed = await alix.contacts.isAllowed(bo.address) + assert(isAllowed, 'bo should be allowed') + return true +}) + +test('can start conversations without consent proofs', async () => { + const bo = await Client.createRandom({ env: 'local' }) + await delayToPropogate() + const alix = await Client.createRandom({ env: 'local' }) + await delayToPropogate() + + const boConvo = await bo.conversations.newConversation(alix.address) + await delayToPropogate() + assert(!boConvo.consentProof, 'consentProof should not exist') + const alixConvo = (await alix.conversations.list())[0] + await delayToPropogate() + assert(!alixConvo.consentProof, 'consentProof should not exist') + await delayToPropogate() + return true +}) diff --git a/example/yarn.lock b/example/yarn.lock index 85f443f16..98edbc3fe 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -4215,7 +4215,7 @@ "@ethersproject/shims@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/shims/-/shims-5.7.0.tgz" + resolved "https://registry.yarnpkg.com/@ethersproject/shims/-/shims-5.7.0.tgz#ee32e543418595774029c5ea6123ea8995e7e154" integrity sha512-WeDptc6oAprov5CCN2LJ/6/+dC9gTonnkdAtLepm/7P5Z+3PRxS5NpfVWmOMs1yE4Vitl2cU8bOPWC0GvGSbVg== "@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": @@ -6843,6 +6843,16 @@ rxjs "^7.8.0" undici "^5.8.1" +"@xmtp/proto@3.54.0": + version "3.54.0" + resolved "https://registry.yarnpkg.com/@xmtp/proto/-/proto-3.54.0.tgz#1b6be9fa2268a51b730bf484af3bf0ebc2621505" + integrity sha512-X0jDRR19/tH0qRB8mM/H/vBueQAK22VZF4QUnDN7TgnbNaOYL5DvSmPfXFH+xzeGKQ5S0zgwc+qFJbI4xoKNHw== + dependencies: + long "^5.2.0" + protobufjs "^7.0.0" + rxjs "^7.8.0" + undici "^5.8.1" + JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" @@ -14498,7 +14508,16 @@ strict-uri-encode@^2.0.0: resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -14572,7 +14591,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -14586,6 +14605,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" @@ -15856,7 +15882,7 @@ word-wrap@~1.2.3: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15874,6 +15900,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" diff --git a/ios/Wrappers/ConversationWrapper.swift b/ios/Wrappers/ConversationWrapper.swift index 5918a33f4..d61100db4 100644 --- a/ios/Wrappers/ConversationWrapper.swift +++ b/ios/Wrappers/ConversationWrapper.swift @@ -17,6 +17,10 @@ struct ConversationWrapper { "metadata": cv2.context.metadata, ] } + var consentProof: String? = nil + if (conversation.consentProof != nil) { + consentProof = try conversation.consentProof?.serializedData().base64EncodedString() + } return [ "clientAddress": client.address, "topic": conversation.topic, @@ -25,7 +29,8 @@ struct ConversationWrapper { "peerAddress": conversation.peerAddress, "version": "DIRECT", "conversationID": conversation.conversationID ?? "", - "keyMaterial": conversation.keyMaterial?.base64EncodedString() ?? "" + "keyMaterial": conversation.keyMaterial?.base64EncodedString() ?? "", + "consentProof": consentProof ?? "" ] } diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index e9d73212d..498061ff0 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -604,7 +604,7 @@ public class XMTPModule: Module { return prepared.messageID } - AsyncFunction("createConversation") { (clientAddress: String, peerAddress: String, contextJson: String) -> String in + AsyncFunction("createConversation") { (clientAddress: String, peerAddress: String, contextJson: String, consentProofBytes: [UInt8]) -> String in guard let client = await clientsManager.getClient(key: clientAddress) else { throw Error.noClient } @@ -612,10 +612,22 @@ public class XMTPModule: Module { do { let contextData = contextJson.data(using: .utf8)! let contextObj = (try? JSONSerialization.jsonObject(with: contextData) as? [String: Any]) ?? [:] - let conversation = try await client.conversations.newConversation(with: peerAddress, context: .init( - conversationID: contextObj["conversationID"] as? String ?? "", - metadata: contextObj["metadata"] as? [String: String] ?? [:] as [String: String] - )) + var consentProofData:ConsentProofPayload? + if consentProofBytes.count != 0 { + do { + consentProofData = try ConsentProofPayload(serializedData: Data(consentProofBytes)) + } catch { + print("Error: \(error)") + } + } + let conversation = try await client.conversations.newConversation( + with: peerAddress, + context: .init( + conversationID: contextObj["conversationID"] as? String ?? "", + metadata: contextObj["metadata"] as? [String: String] ?? [:] as [String: String] + ), + consentProofPayload:consentProofData + ) return try ConversationWrapper.encode(conversation, client: client) } catch { diff --git a/package.json b/package.json index b517aed2e..595978c2f 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@ethersproject/bytes": "^5.7.0", "@msgpack/msgpack": "^3.0.0-beta2", "@noble/hashes": "^1.3.3", - "@xmtp/proto": "^3.44.0", + "@xmtp/proto": "3.54.0", "buffer": "^6.0.3", "text-encoding": "^0.7.0" }, diff --git a/src/index.ts b/src/index.ts index af5d07bf3..580bde3b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { content, keystore } from '@xmtp/proto' +import { content, invitation, keystore } from '@xmtp/proto' import { EventEmitter, NativeModulesProxy } from 'expo-modules-core' import { Client } from '.' @@ -430,15 +430,22 @@ export async function createConversation< >( client: Client, peerAddress: string, - context?: ConversationContext + context?: ConversationContext, + consentProofPayload?: invitation.ConsentProofPayload ): Promise> { + const consentProofData = consentProofPayload + ? Array.from( + invitation.ConsentProofPayload.encode(consentProofPayload).finish() + ) + : [] return new Conversation( client, JSON.parse( await XMTPModule.createConversation( client.address, getAddress(peerAddress), - JSON.stringify(context || {}) + JSON.stringify(context || {}), + consentProofData ) ) ) diff --git a/src/lib/Conversation.ts b/src/lib/Conversation.ts index e9daacc26..16b6a3c15 100644 --- a/src/lib/Conversation.ts +++ b/src/lib/Conversation.ts @@ -1,17 +1,28 @@ +import { invitation } from '@xmtp/proto' +import { Buffer } from 'buffer' + import { ConversationVersion, ConversationContainer, } from './ConversationContainer' +import { DecodedMessage } from './DecodedMessage' import { ConversationSendPayload } from './types/ConversationCodecs' import { DefaultContentTypes } from './types/DefaultContentType' import { EventTypes } from './types/EventTypes' import { SendOptions } from './types/SendOptions' import * as XMTP from '../index' -import { - ConversationContext, - DecodedMessage, - PreparedLocalMessage, -} from '../index' +import { ConversationContext, PreparedLocalMessage } from '../index' + +export interface ConversationParams { + createdAt: number + context?: ConversationContext + topic: string + peerAddress?: string + version: string + conversationID?: string + keyMaterial?: string + consentProof?: string +} export class Conversation implements ConversationContainer @@ -27,25 +38,26 @@ export class Conversation * Base64 encoded key material for the conversation. */ keyMaterial?: string | undefined + /** + * Proof of consent for the conversation, used when a user is subscribing to broadcasts. + */ + consentProof?: invitation.ConsentProofPayload | undefined - constructor( - client: XMTP.Client, - params: { - createdAt: number - context?: ConversationContext - topic: string - peerAddress: string - conversationID?: string | undefined - keyMaterial?: string | undefined - } - ) { + constructor(client: XMTP.Client, params: ConversationParams) { this.client = client this.createdAt = params.createdAt this.context = params.context this.topic = params.topic - this.peerAddress = params.peerAddress + this.peerAddress = params.peerAddress ?? '' this.conversationID = params.conversationID this.keyMaterial = params.keyMaterial + try { + if (params?.consentProof) { + this.consentProof = invitation.ConsentProofPayload.decode( + new Uint8Array(Buffer.from(params.consentProof, 'base64')) + ) + } + } catch {} } get clientAddress(): string { diff --git a/src/lib/ConversationContainer.ts b/src/lib/ConversationContainer.ts index 82034216a..d91dbed3f 100644 --- a/src/lib/ConversationContainer.ts +++ b/src/lib/ConversationContainer.ts @@ -11,6 +11,6 @@ export interface ConversationContainer< > { client: XMTP.Client createdAt: number - version: ConversationVersion topic: string + version: ConversationVersion } diff --git a/src/lib/Conversations.ts b/src/lib/Conversations.ts index 9dc23f1b4..cc8e2abad 100644 --- a/src/lib/Conversations.ts +++ b/src/lib/Conversations.ts @@ -1,7 +1,7 @@ -import type { keystore } from '@xmtp/proto' +import type { invitation, keystore } from '@xmtp/proto' import { Client } from './Client' -import { Conversation } from './Conversation' +import { Conversation, ConversationParams } from './Conversation' import { ConversationVersion, ConversationContainer, @@ -65,13 +65,15 @@ export default class Conversations< */ async newConversation( peerAddress: string, - context?: ConversationContext + context?: ConversationContext, + consentProof?: invitation.ConsentProofPayload ): Promise> { const checksumAddress = getAddress(peerAddress) return await XMTPModule.createConversation( this.client, checksumAddress, - context + context, + consentProof ) } @@ -200,7 +202,7 @@ export default class Conversations< conversation, }: { clientAddress: string - conversation: Conversation + conversation: ConversationParams }) => { if (clientAddress !== this.client.address) { return @@ -256,7 +258,7 @@ export default class Conversations< return await callback( new Conversation( this.client, - conversationContainer as Conversation + conversationContainer as ConversationParams ) ) } diff --git a/yarn.lock b/yarn.lock index d14629be3..5a80a1db8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2578,10 +2578,10 @@ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3" integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g== -"@xmtp/proto@^3.44.0": - version "3.44.0" - resolved "https://registry.yarnpkg.com/@xmtp/proto/-/proto-3.44.0.tgz#6750f47869de508c2f3db596bb952e84062988bd" - integrity sha512-5M23JsxONb/qluZF6QMjoXHT67xDRR7pWwHTd1tfw59jS+jXTxm/+v8/UeSOoK47PBSJhDD5I90bAeA5NrLRig== +"@xmtp/proto@3.54.0": + version "3.54.0" + resolved "https://registry.yarnpkg.com/@xmtp/proto/-/proto-3.54.0.tgz#1b6be9fa2268a51b730bf484af3bf0ebc2621505" + integrity sha512-X0jDRR19/tH0qRB8mM/H/vBueQAK22VZF4QUnDN7TgnbNaOYL5DvSmPfXFH+xzeGKQ5S0zgwc+qFJbI4xoKNHw== dependencies: long "^5.2.0" protobufjs "^7.0.0" @@ -7880,7 +7880,16 @@ string-length@^5.0.1: char-regex "^2.0.0" strip-ansi "^7.0.1" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -7954,7 +7963,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -8667,7 +8683,16 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==