From d695a4f5e567cf3e26106baede716fef0ececf26 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 8 Aug 2024 11:01:52 +0100 Subject: [PATCH 1/5] Show did history as JSON --- packages/atproto-browser/app/at/page.tsx | 90 ++++++++++++++++-------- packages/atproto-browser/package.json | 1 + pnpm-lock.yaml | 13 ++++ 3 files changed, 74 insertions(+), 30 deletions(-) diff --git a/packages/atproto-browser/app/at/page.tsx b/packages/atproto-browser/app/at/page.tsx index 4d5b0ab4..8296058b 100644 --- a/packages/atproto-browser/app/at/page.tsx +++ b/packages/atproto-browser/app/at/page.tsx @@ -14,6 +14,7 @@ import { CollectionItems } from "./_lib/collection"; import { SWRConfig } from "swr"; import { listRecords } from "@/lib/atproto"; import { verifyRecords } from "@atproto/repo"; +import { ErrorBoundary } from "react-error-boundary"; const didResolver = new DidResolver({}); const resolveDid = cache((did: string) => didResolver.resolve(did)); @@ -57,7 +58,14 @@ export default async function AtPage({

{handle} ({didDocument.id})

- + + + Loading history...

}> + Failed to fetch history.

}> +

History

+ +
+
); } @@ -117,11 +125,10 @@ export default async function AtPage({
- Author: {handle} ({didStr} - ) + Author: {handle} ({didStr}) - +

@@ -196,7 +203,27 @@ async function RecordVerificationBadge({ } } -async function Author({ did }: { did: string }) { +async function DidSummary({ did }: { did: string }) { + return ( + <> + Failed to fetch collections for {did}.

} + > +

Collections

+ + +

DID Doc

+ + + ); +} + +async function DidDoc({ did }: { did: string }) { + const didDocument = await resolveDid(did); + return ; +} + +async function DidCollections({ did }: { did: string }) { const didDocument = await resolveDid(did); if (!didDocument) { throw new Error(`Could not resolve DID: ${did}`); @@ -216,11 +243,8 @@ async function Author({ did }: { did: string }) { }); if (!response.ok) { - return ( -
- Failed to fetch collections: {response.statusText}. URL:{" "} - {describeRepoUrl.toString()} -
+ throw new Error( + `Failed to fetch collections: ${response.statusText}. URL: ${describeRepoUrl.toString()}`, ); } @@ -229,29 +253,35 @@ async function Author({ did }: { did: string }) { }; return ( - <> -

Collections

-
    - {collections.length === 0 ? ( -

    No collections.

    - ) : ( - collections.map((nsid) => { - const collectionUri = `at://${[did, nsid].join("/")}`; - - return ( -
  • - {nsid} -
  • - ); - }) - )} -
-

DID Doc

- - +
    + {collections.length === 0 ? ( +

    No collections.

    + ) : ( + collections.map((nsid) => { + const collectionUri = `at://${[did, nsid].join("/")}`; + + return ( +
  • + {nsid} +
  • + ); + }) + )} +
); } +async function DidHistory({ did }: { did: string }) { + const response = await fetch(`https://plc.directory/${did}/log/audit`); + if (!response.ok) { + throw new Error(`Failed to fetch history: ${response.statusText}`); + } + + const history = (await response.json()) as JSONType; + + return ; +} + function naiveAtUriCheck(atUri: string) { if (!atUri.startsWith("at://")) { return false; diff --git a/packages/atproto-browser/package.json b/packages/atproto-browser/package.json index ad13e943..60d5bc03 100644 --- a/packages/atproto-browser/package.json +++ b/packages/atproto-browser/package.json @@ -16,6 +16,7 @@ "next": "15.0.0-rc.0", "react": "19.0.0-rc-f994737d14-20240522", "react-dom": "19.0.0-rc-f994737d14-20240522", + "react-error-boundary": "^4.0.13", "swr": "2.2.6-beta.4", "zod": "^3.23.8" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02280c70..2552b8c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: react-dom: specifier: 19.0.0-rc-f994737d14-20240522 version: 19.0.0-rc-f994737d14-20240522(react@19.0.0-rc-f994737d14-20240522) + react-error-boundary: + specifier: ^4.0.13 + version: 4.0.13(react@19.0.0-rc-f994737d14-20240522) swr: specifier: 2.2.6-beta.4 version: 2.2.6-beta.4(react@19.0.0-rc-f994737d14-20240522) @@ -4702,6 +4705,11 @@ packages: peerDependencies: react: 19.0.0-rc-f994737d14-20240522 + react-error-boundary@4.0.13: + resolution: {integrity: sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==} + peerDependencies: + react: '>=16.13.1' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -10956,6 +10964,11 @@ snapshots: react: 19.0.0-rc-f994737d14-20240522 scheduler: 0.25.0-rc-f994737d14-20240522 + react-error-boundary@4.0.13(react@19.0.0-rc-f994737d14-20240522): + dependencies: + '@babel/runtime': 7.24.6 + react: 19.0.0-rc-f994737d14-20240522 + react-is@16.13.1: {} react-is@17.0.2: {} From 43badeddb2293f8455fb2b81b2ace8a814197bd7 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 8 Aug 2024 19:24:03 +0100 Subject: [PATCH 2/5] Trigger build From 4b79902b42d8257868f078799e17dbd7d66e67e1 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 8 Aug 2024 19:25:33 +0100 Subject: [PATCH 3/5] Trigger build From ab81323fdedc24469d37d8fda28060f6e613a89d Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Mon, 12 Aug 2024 21:01:25 +0100 Subject: [PATCH 4/5] Show history as human readable --- packages/atproto-browser/app/at/page.tsx | 256 ++++++++++++++++++++++- pnpm-lock.yaml | 64 +++++- 2 files changed, 314 insertions(+), 6 deletions(-) diff --git a/packages/atproto-browser/app/at/page.tsx b/packages/atproto-browser/app/at/page.tsx index 8296058b..901dfa03 100644 --- a/packages/atproto-browser/app/at/page.tsx +++ b/packages/atproto-browser/app/at/page.tsx @@ -7,7 +7,7 @@ import { } from "@atproto/identity"; import { AtUri, isValidHandle } from "@atproto/syntax"; import { isDid } from "@atproto/did"; -import { cache, Suspense } from "react"; +import { cache, Fragment, Suspense } from "react"; import Link from "next/link"; import { AtBlob } from "./_lib/at-blob"; import { CollectionItems } from "./_lib/collection"; @@ -15,6 +15,7 @@ import { SWRConfig } from "swr"; import { listRecords } from "@/lib/atproto"; import { verifyRecords } from "@atproto/repo"; import { ErrorBoundary } from "react-error-boundary"; +import { z } from "zod"; const didResolver = new DidResolver({}); const resolveDid = cache((did: string) => didResolver.resolve(did)); @@ -271,15 +272,264 @@ async function DidCollections({ did }: { did: string }) { ); } +const PlcLogAuditResponse = z.array( + z.object({ + createdAt: z + .string() + .datetime() + .transform((x) => new Date(x)), + operation: z.object({ + sig: z.string(), + prev: z.string().nullable(), + type: z.literal("plc_operation"), + services: z.record( + z.object({ + type: z.string(), + endpoint: z.string(), + }), + ), + alsoKnownAs: z.array(z.string()), + rotationKeys: z.array(z.string()), + verificationMethods: z.record(z.string()), + }), + }), +); + +const utcDateFormatter = new Intl.DateTimeFormat("en-US", { + dateStyle: "medium", + timeStyle: "short", + timeZone: "UTC", +}); + +function isNotNull(x: T | null): x is T { + return x !== null; +} + async function DidHistory({ did }: { did: string }) { const response = await fetch(`https://plc.directory/${did}/log/audit`); if (!response.ok) { throw new Error(`Failed to fetch history: ${response.statusText}`); } - const history = (await response.json()) as JSONType; + const auditLog = PlcLogAuditResponse.parse(await response.json()); - return ; + return ( +
    + {auditLog.map((previous, index) => { + const entry = auditLog[index + 1]; + if (!entry) { + return null; + } + + const alsoKnownAsAdded = entry.operation.alsoKnownAs.filter( + (x) => !previous.operation.alsoKnownAs.includes(x), + ); + const alsoKnownAsRemoved = previous.operation.alsoKnownAs.filter( + (x) => !entry.operation.alsoKnownAs.includes(x), + ); + + const servicesChanged = Object.entries(entry.operation.services) + .map(([id, service]) => { + const previousService = previous.operation.services[id]; + if (!previousService) return null; + return { + id, + type: + service.type !== previousService.type + ? { + from: previousService.type, + to: service.type, + } + : null, + endpoint: + service.endpoint !== previousService.endpoint + ? { + from: previousService.endpoint, + to: service.endpoint, + } + : null, + }; + }) + .filter(isNotNull); + + const servicesAdded = Object.entries(entry.operation.services).filter( + ([id]) => !previous.operation.services[id], + ); + const servicesRemoved = Object.entries( + previous.operation.services, + ).filter(([id]) => !entry.operation.services[id]); + + const rotationKeysAdded = entry.operation.rotationKeys.filter( + (x) => !previous.operation.rotationKeys.includes(x), + ); + const rotationKeysRemoved = previous.operation.rotationKeys.filter( + (x) => !entry.operation.rotationKeys.includes(x), + ); + + const verificationMethodsChanged = Object.entries( + entry.operation.verificationMethods, + ) + .map(([id, key]) => { + const previousKey = previous.operation.verificationMethods[id]; + if (!previousKey) return null; + if (key === previousKey) return null; + return { + id, + from: previousKey, + to: key, + }; + }) + .filter(isNotNull); + const verificationMethodsAdded = Object.entries( + entry.operation.verificationMethods, + ).filter(([id]) => !previous.operation.verificationMethods[id]); + const verificationMethodsRemoved = Object.entries( + previous.operation.verificationMethods, + ).filter(([id]) => !entry.operation.verificationMethods[id]); + + return ( + // eslint-disable-next-line react/no-array-index-key +
  1. +

    + Change created at {utcDateFormatter.format(entry.createdAt)} (UTC) +

    +
      + {alsoKnownAsAdded.length === 1 && + alsoKnownAsRemoved.length === 1 ? ( +
    • + Alias changed from{" "} + + {alsoKnownAsRemoved[0]} + {" "} + to{" "} + + {alsoKnownAsAdded[0]} + +
    • + ) : ( + <> + {alsoKnownAsAdded.length > 0 && ( +
    • + Alias added:{" "} + {alsoKnownAsAdded.flatMap((aka) => [ + + {aka} + , + ", ", + ])} +
    • + )} + {alsoKnownAsRemoved.length > 0 && ( +
    • + Alias removed:{" "} + {alsoKnownAsRemoved.flatMap((aka) => [ + + {aka} + , + ", ", + ])} +
    • + )} + + )} + {servicesChanged.length > 0 && + servicesChanged.map((service) => ( + + {!!service.type && ( +
    • + Service "{service.id}" changed type from + " + {service.type.from}" to "{service.type.to} + " +
    • + )} + {!!service.endpoint && ( +
    • + Service "{service.id}" changed endpoint from{" "} + + {service.endpoint.from} + {" "} + to{" "} + {service.endpoint.to} +
    • + )} +
      + ))} + {servicesAdded.length > 0 && ( +
    • + Services added:{" "} + {servicesAdded.flatMap(([id, service]) => [ + + {id} ({service.type}) + , + ", ", + ])} +
    • + )} + {servicesRemoved.length > 0 && ( +
    • + Services removed:{" "} + {servicesRemoved.flatMap(([id, service]) => [ + + {id} ({service.type}) + , + ", ", + ])} +
    • + )} + {rotationKeysAdded.length > 0 && ( +
    • + Rotation keys added:{" "} + {rotationKeysAdded.flatMap((key) => [ + {key}, + ", ", + ])} +
    • + )} + {rotationKeysRemoved.length > 0 && ( +
    • + Rotation keys removed:{" "} + {rotationKeysRemoved.flatMap((key) => [ + {key}, + ", ", + ])} +
    • + )} + {verificationMethodsChanged.length > 0 && + verificationMethodsChanged.map((method) => ( +
    • + Verification method "{method.id}" changed from{" "} + {method.from} to {method.to} +
    • + ))} + {verificationMethodsAdded.length > 0 && ( +
    • + Verification methods added:{" "} + {verificationMethodsAdded.flatMap(([id, key]) => [ + + {key} ("{id}") + , + ", ", + ])} +
    • + )} + {verificationMethodsRemoved.length > 0 && ( +
    • + Verification methods removed:{" "} + {verificationMethodsRemoved.flatMap(([id, key]) => [ + + {key} ("{id}") + , + ", ", + ])} +
    • + )} +
    +
  2. + ); + })} +
+ ); } function naiveAtUriCheck(atUri: string) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2552b8c9..395808c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,7 +31,7 @@ importers: version: 0.3.0 next: specifier: 15.0.0-rc.0 - version: 15.0.0-rc.0(@babel/core@7.24.7)(babel-plugin-react-compiler@0.0.0-experimental-938cd9a-20240601)(react-dom@19.0.0-rc-f994737d14-20240522(react@19.0.0-rc-f994737d14-20240522))(react@19.0.0-rc-f994737d14-20240522) + version: 15.0.0-rc.0(react-dom@19.0.0-rc-f994737d14-20240522(react@19.0.0-rc-f994737d14-20240522))(react@19.0.0-rc-f994737d14-20240522) react: specifier: 19.0.0-rc-f994737d14-20240522 version: 19.0.0-rc-f994737d14-20240522 @@ -9292,7 +9292,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.2(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -9363,7 +9363,7 @@ snapshots: enhanced-resolve: 5.16.1 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -9467,6 +9467,33 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + dependencies: + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + hasown: 2.0.2 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.2) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.5.2) @@ -10553,6 +10580,32 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@15.0.0-rc.0(react-dom@19.0.0-rc-f994737d14-20240522(react@19.0.0-rc-f994737d14-20240522))(react@19.0.0-rc-f994737d14-20240522): + dependencies: + '@next/env': 15.0.0-rc.0 + '@swc/helpers': 0.5.11 + busboy: 1.6.0 + caniuse-lite: 1.0.30001637 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 19.0.0-rc-f994737d14-20240522 + react-dom: 19.0.0-rc-f994737d14-20240522(react@19.0.0-rc-f994737d14-20240522) + styled-jsx: 5.1.3(react@19.0.0-rc-f994737d14-20240522) + optionalDependencies: + '@next/swc-darwin-arm64': 15.0.0-rc.0 + '@next/swc-darwin-x64': 15.0.0-rc.0 + '@next/swc-linux-arm64-gnu': 15.0.0-rc.0 + '@next/swc-linux-arm64-musl': 15.0.0-rc.0 + '@next/swc-linux-x64-gnu': 15.0.0-rc.0 + '@next/swc-linux-x64-musl': 15.0.0-rc.0 + '@next/swc-win32-arm64-msvc': 15.0.0-rc.0 + '@next/swc-win32-ia32-msvc': 15.0.0-rc.0 + '@next/swc-win32-x64-msvc': 15.0.0-rc.0 + sharp: 0.33.4 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + node-abi@3.63.0: dependencies: semver: 7.6.2 @@ -11462,6 +11515,11 @@ snapshots: client-only: 0.0.1 react: 19.0.0-rc-3563387fe3-20240621 + styled-jsx@5.1.3(react@19.0.0-rc-f994737d14-20240522): + dependencies: + client-only: 0.0.1 + react: 19.0.0-rc-f994737d14-20240522 + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.5 From c03878b3718f660b9a63c683c00f70a98ab5209f Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Mon, 12 Aug 2024 21:27:08 +0100 Subject: [PATCH 5/5] Handle create and tombstone --- packages/atproto-browser/app/at/page.tsx | 94 +++++++++++++++--------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/packages/atproto-browser/app/at/page.tsx b/packages/atproto-browser/app/at/page.tsx index 901dfa03..19d51d38 100644 --- a/packages/atproto-browser/app/at/page.tsx +++ b/packages/atproto-browser/app/at/page.tsx @@ -278,20 +278,32 @@ const PlcLogAuditResponse = z.array( .string() .datetime() .transform((x) => new Date(x)), - operation: z.object({ - sig: z.string(), - prev: z.string().nullable(), - type: z.literal("plc_operation"), - services: z.record( - z.object({ - type: z.string(), - endpoint: z.string(), - }), - ), - alsoKnownAs: z.array(z.string()), - rotationKeys: z.array(z.string()), - verificationMethods: z.record(z.string()), - }), + operation: z.union([ + z.object({ + type: z.literal("plc_operation"), + sig: z.string(), + prev: z.string().nullable(), + services: z.record( + z.object({ + type: z.string(), + endpoint: z.string(), + }), + ), + alsoKnownAs: z.array(z.string()), + rotationKeys: z.array(z.string()), + verificationMethods: z.record(z.string()), + }), + z.object({ + type: z.literal("create"), + signingKey: z.string(), + recoveryKey: z.string(), + handle: z.string(), + service: z.string(), + }), + z.object({ + type: z.literal("plc_tombstone"), + }), + ]), }), ); @@ -316,21 +328,35 @@ async function DidHistory({ did }: { did: string }) { return (
    {auditLog.map((previous, index) => { + const previousOperation = previous.operation; + if (previousOperation.type !== "plc_operation") { + return ( + // eslint-disable-next-line react/no-array-index-key +
  1. + Change created at {utcDateFormatter.format(previous.createdAt)}{" "} + (UTC) of type "{previousOperation.type}". +
  2. + ); + } const entry = auditLog[index + 1]; if (!entry) { return null; } + const entryOperation = entry.operation; + if (entryOperation.type !== "plc_operation") { + return null; + } - const alsoKnownAsAdded = entry.operation.alsoKnownAs.filter( - (x) => !previous.operation.alsoKnownAs.includes(x), + const alsoKnownAsAdded = entryOperation.alsoKnownAs.filter( + (x) => !previousOperation.alsoKnownAs.includes(x), ); - const alsoKnownAsRemoved = previous.operation.alsoKnownAs.filter( - (x) => !entry.operation.alsoKnownAs.includes(x), + const alsoKnownAsRemoved = previousOperation.alsoKnownAs.filter( + (x) => !entryOperation.alsoKnownAs.includes(x), ); - const servicesChanged = Object.entries(entry.operation.services) + const servicesChanged = Object.entries(entryOperation.services) .map(([id, service]) => { - const previousService = previous.operation.services[id]; + const previousService = previousOperation.services[id]; if (!previousService) return null; return { id, @@ -352,25 +378,25 @@ async function DidHistory({ did }: { did: string }) { }) .filter(isNotNull); - const servicesAdded = Object.entries(entry.operation.services).filter( - ([id]) => !previous.operation.services[id], + const servicesAdded = Object.entries(entryOperation.services).filter( + ([id]) => !previousOperation.services[id], ); const servicesRemoved = Object.entries( - previous.operation.services, - ).filter(([id]) => !entry.operation.services[id]); + previousOperation.services, + ).filter(([id]) => !entryOperation.services[id]); - const rotationKeysAdded = entry.operation.rotationKeys.filter( - (x) => !previous.operation.rotationKeys.includes(x), + const rotationKeysAdded = entryOperation.rotationKeys.filter( + (x) => !previousOperation.rotationKeys.includes(x), ); - const rotationKeysRemoved = previous.operation.rotationKeys.filter( - (x) => !entry.operation.rotationKeys.includes(x), + const rotationKeysRemoved = previousOperation.rotationKeys.filter( + (x) => !entryOperation.rotationKeys.includes(x), ); const verificationMethodsChanged = Object.entries( - entry.operation.verificationMethods, + entryOperation.verificationMethods, ) .map(([id, key]) => { - const previousKey = previous.operation.verificationMethods[id]; + const previousKey = previousOperation.verificationMethods[id]; if (!previousKey) return null; if (key === previousKey) return null; return { @@ -381,11 +407,11 @@ async function DidHistory({ did }: { did: string }) { }) .filter(isNotNull); const verificationMethodsAdded = Object.entries( - entry.operation.verificationMethods, - ).filter(([id]) => !previous.operation.verificationMethods[id]); + entryOperation.verificationMethods, + ).filter(([id]) => !previousOperation.verificationMethods[id]); const verificationMethodsRemoved = Object.entries( - previous.operation.verificationMethods, - ).filter(([id]) => !entry.operation.verificationMethods[id]); + previousOperation.verificationMethods, + ).filter(([id]) => !entryOperation.verificationMethods[id]); return ( // eslint-disable-next-line react/no-array-index-key