From e09dcfad7f4be6657d9eeb73be772a281de5ca3a Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Wed, 17 Jan 2024 16:15:48 -0600 Subject: [PATCH] revert: remove CompositeCodec BREAKING CHANGE: This removes a feature from the SDK and will break any consumer that rely on it. --- README.md | 7 -- src/codecs/Composite.ts | 118 ---------------------------------- src/index.ts | 2 - test/Client.test.ts | 47 ++++++++++++-- test/codecs/Composite.test.ts | 62 ------------------ 5 files changed, 43 insertions(+), 193 deletions(-) delete mode 100644 src/codecs/Composite.ts delete mode 100644 test/codecs/Composite.test.ts diff --git a/README.md b/README.md index cd2e4144c..43e80794b 100644 --- a/README.md +++ b/README.md @@ -340,13 +340,6 @@ As shown in the example above, you must provide a `contentFallback` value. Use i Additional codecs can be configured through the `ClientOptions` parameter of `Client.create`. The `codecs` option is a list of codec instances that should be added to the default set of codecs (currently only the `TextCodec`). If a codec is added for a content type that is already in the default set, it will replace the original codec. -```ts -// Adding support for `xmtp.org/composite` content type -import { CompositeCodec } from '@xmtp/xmtp-js' - -const xmtp = Client.create(wallet, { codecs: [new CompositeCodec()] }) -``` - To learn more about how to build a custom content type, see [Build a custom content type](https://xmtp.org/docs/content-types/introduction#create-custom-content-types). Custom codecs and content types may be proposed as interoperable standards through XRCs. To learn about the custom content type proposal process, see [XIP-5](https://github.com/xmtp/XIPs/blob/main/XIPs/xip-5-message-content-types.md). diff --git a/src/codecs/Composite.ts b/src/codecs/Composite.ts deleted file mode 100644 index e5a115e6f..000000000 --- a/src/codecs/Composite.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { composite as proto } from '@xmtp/proto' -import { - ContentTypeId, - type CodecRegistry, - type ContentCodec, - type EncodedContent, -} from '@/MessageContent' - -// xmtp.org/composite -// -// Composite is a generic sequence of multiple parts of arbitrary content type. -// It can be nested arbitrarily (composite of composites). - -export const ContentTypeComposite = new ContentTypeId({ - authorityId: 'xmtp.org', - typeId: 'composite', - versionMajor: 1, - versionMinor: 0, -}) - -// Composite type defines the expected structure of values -// that can be processed by the CompositeCodec -export type Composite = - | { - type: ContentTypeId - // eslint-disable-next-line @typescript-eslint/no-explicit-any - content: any - } - | { parts: Composite[] } - -// CompositeCodec implements encoding/decoding of Composite values. -// Register this codec with the Client if you want support for Composite content. -export class CompositeCodec implements ContentCodec { - get contentType(): ContentTypeId { - return ContentTypeComposite - } - - encode(content: Composite, codecs: CodecRegistry): EncodedContent { - const part = this.toProto(content, codecs) - let composite: proto.Composite - if (part.composite) { - composite = part.composite - } else { - composite = { parts: [part] } - } - const bytes = proto.Composite.encode(composite).finish() - return { - type: ContentTypeComposite, - parameters: {}, - content: bytes, - } - } - - decode(content: EncodedContent, codecs: CodecRegistry): Composite { - return this.fromProto( - { composite: proto.Composite.decode(content.content), part: undefined }, - codecs - ) - } - - private toProto( - content: Composite, - codecs: CodecRegistry - ): proto.Composite_Part { - if ('type' in content) { - const codec = codecs.codecFor(content.type) - if (!codec) { - throw new Error(`missing codec for part type ${content.type}`) - } - return { - part: codec.encode(content.content, codecs), - composite: undefined, - } - } - const parts = new Array() - for (const part of content.parts) { - parts.push(this.toProto(part, codecs)) - } - return { composite: { parts }, part: undefined } - } - - private fromProto( - content: proto.Composite_Part, - codecs: CodecRegistry - ): Composite { - if (content.part) { - if (!content.part.type) { - throw new Error('missing part content type') - } - const contentType = new ContentTypeId(content.part.type) - const codec = codecs.codecFor(contentType) - if (!codec) { - throw new Error(`missing codec for part type ${contentType}`) - } - return { - type: contentType, - content: codec.decode(content.part as EncodedContent, codecs), - } - } - if (!content.composite) { - throw new Error('invalid composite') - } - const parts = new Array() - for (const part of content.composite.parts) { - parts.push(this.fromProto(part, codecs)) - } - return { parts } - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - fallback(content: Composite): string | undefined { - return undefined - } - - shouldPush() { - return false - } -} diff --git a/src/index.ts b/src/index.ts index 4e241590d..a037596fa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -50,8 +50,6 @@ export type { } from './MessageContent' export { ContentTypeId, ContentTypeFallback } from './MessageContent' export { TextCodec, ContentTypeText } from './codecs/Text' -export type { Composite } from './codecs/Composite' -export { CompositeCodec, ContentTypeComposite } from './codecs/Composite' export type { ApiClient, QueryParams, diff --git a/test/Client.test.ts b/test/Client.test.ts index 21c38b40e..302ecb930 100644 --- a/test/Client.test.ts +++ b/test/Client.test.ts @@ -7,7 +7,6 @@ import { mainnet } from 'viem/chains' import { assert, vi } from 'vitest' import HttpApiClient, { ApiUrls } from '@/ApiClient' import Client, { Compression, type ClientOptions } from '@/Client' -import { CompositeCodec } from '@/codecs/Composite' import { ContentTypeText, TextCodec } from '@/codecs/Text' import { PrivateKey } from '@/crypto/PrivateKey' import { PrivateKeyBundleV1 } from '@/crypto/PrivateKeyBundle' @@ -16,6 +15,11 @@ import LocalStoragePonyfill from '@/keystore/persistence/LocalStoragePonyfill' import TopicPersistence from '@/keystore/persistence/TopicPersistence' import NetworkKeyManager from '@/keystore/providers/NetworkKeyManager' import NetworkKeystoreProvider from '@/keystore/providers/NetworkKeystoreProvider' +import { + ContentTypeId, + type ContentCodec, + type EncodedContent, +} from '@/MessageContent' import type { EnvelopeWithMessage } from '@/utils/async' import { buildUserContactTopic } from '@/utils/topic' import { ContentTypeTestKey, TestKeyCodec } from './ContentTypeTestKey' @@ -420,15 +424,50 @@ describe('ClientOptions', () => { }) it('allows you to use custom content types', async () => { + const ContentTypeCustom = new ContentTypeId({ + authorityId: 'xmtp.org', + typeId: 'text', + versionMajor: 1, + versionMinor: 0, + }) + class CustomCodec implements ContentCodec<{ custom: string }> { + get contentType(): ContentTypeId { + return ContentTypeCustom + } + + encode(content: { custom: string }): EncodedContent { + return { + type: ContentTypeText, + parameters: {}, + content: new TextEncoder().encode(JSON.stringify(content)), + } + } + + decode(content: EncodedContent): { custom: string } { + const decodedContent = new TextDecoder().decode(content.content) + const parsedContent = JSON.parse(decodedContent) as { custom: string } + return { + custom: parsedContent.custom, + } + } + + fallback() { + return undefined + } + + shouldPush() { + return false + } + } + const client = await Client.create(newWallet(), { - env: 'local', - codecs: [new CompositeCodec()], + codecs: [new CustomCodec()], }) const other = await Client.create(newWallet(), { env: 'local' }) const convo = await client.conversations.newConversation(other.address) expect(convo).toBeTruthy() // This will have a type error if the codecs field isn't being respected - await convo.send({ parts: [{ type: ContentTypeText, content: 'foo' }] }) + await convo.send({ custom: 'test' }) }) }) diff --git a/test/codecs/Composite.test.ts b/test/codecs/Composite.test.ts deleted file mode 100644 index 4790e524b..000000000 --- a/test/codecs/Composite.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { CompositeCodec, ContentTypeComposite } from '@/codecs/Composite' -import { ContentTypeText } from '@/codecs/Text' -import { CodecRegistry } from '@test/helpers' - -describe('CompositeType', () => { - const codecs = new CodecRegistry() - const codec = new CompositeCodec() - codecs.registerCodec(codec) - it('simple composite', async () => { - const content = { - parts: [ - { type: ContentTypeText, content: 'hello' }, - { type: ContentTypeText, content: 'bye' }, - ], - } - const encoded = codec.encode(content, codecs) - expect(encoded.type.sameAs(ContentTypeComposite)).toBe(true) - const decoded = codec.decode(encoded, codecs) - expect(decoded).toEqual(content) - }) - it('nested composite', async () => { - const content = { - parts: [ - { type: ContentTypeText, content: 'one' }, - { - parts: [ - { type: ContentTypeText, content: 'two' }, - { - parts: [{ type: ContentTypeText, content: 'three' }], - }, - ], - }, - { - parts: [ - { type: ContentTypeText, content: 'four' }, - { - parts: [{ type: ContentTypeText, content: 'five' }], - }, - ], - }, - ], - } - const encoded = codec.encode(content, codecs) - expect(encoded.type.sameAs(ContentTypeComposite)).toBe(true) - const decoded = codec.decode(encoded, codecs) - expect(decoded).toEqual(content) - }) - - it('not quite composite decodes as single part composite', async () => { - const content = { type: ContentTypeText, content: 'one' } - const encoded = codec.encode(content, codecs) - expect(encoded.type.sameAs(ContentTypeComposite)).toBe(true) - const decoded = codec.decode(encoded, codecs) - expect(decoded).toEqual({ parts: [content] }) - }) - - it('definitely not a composite', () => { - const codec = codecs.codecFor(ContentTypeComposite) - expect(codec).toBeTruthy() - expect(() => codec!.encode('definitely not a composite', codecs)).toThrow() - }) -})