From 153461c57ad8fec7f365c007f7b8c548c404b7b3 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Fri, 1 Nov 2024 11:05:28 -0500 Subject: [PATCH 1/4] test: add more coverage for normalise-dag-node.ts --- package-lock.json | 97 +++++++++++++ package.json | 2 + src/lib/normalise-dag-node.ts | 3 +- src/lib/normalize-dag-node.test.ts | 222 +++++++++++++++++++++-------- vitest.config.js | 31 +++- 5 files changed, 292 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2062ee78..b2a216b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,7 @@ "@types/react-helmet": "^6.1.11", "@types/react-virtualized": "^9.21.30", "@vitejs/plugin-react": "^4.3.2", + "@vitest/coverage-v8": "^2.1.3", "aegir": "^44.1.4", "concurrently": "^9.0.1", "eslint-config-ipfs": "^7.0.6", @@ -8634,6 +8635,90 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz", + "integrity": "sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.6", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.11", + "magicast": "^0.3.4", + "std-env": "^3.7.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.3", + "vitest": "2.1.3" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vitest/coverage-v8/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@vitest/expect": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", @@ -22299,6 +22384,18 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", diff --git a/package.json b/package.json index 94098996..173a5464 100644 --- a/package.json +++ b/package.json @@ -179,6 +179,7 @@ "test": "run-s test:unit test:storybook:ci test:consumer", "test:node": "run-s test:unit", "test:unit": "vitest run --environment=node", + "test:unit:cov": "vitest run --environment=node --coverage", "storybook": "storybook dev", "storybook:build": "storybook build", "test:consumer": "run-s test:consumer:webui test:consumer:ipld.io", @@ -232,6 +233,7 @@ "@types/react-helmet": "^6.1.11", "@types/react-virtualized": "^9.21.30", "@vitejs/plugin-react": "^4.3.2", + "@vitest/coverage-v8": "^2.1.3", "aegir": "^44.1.4", "concurrently": "^9.0.1", "eslint-config-ipfs": "^7.0.6", diff --git a/src/lib/normalise-dag-node.ts b/src/lib/normalise-dag-node.ts index ee5d8231..d9e299f4 100644 --- a/src/lib/normalise-dag-node.ts +++ b/src/lib/normalise-dag-node.ts @@ -25,7 +25,7 @@ function isDagPbNode (node: dagNode | PBNode, cid: string): node is PBNode { * @param {string} cidStr - the cid string passed to `ipfs.dag.get` * @returns {import('../types').NormalizedDagNode} */ -export default function normaliseDagNode (node: dagNode | PBNode, cidStr: string): NormalizedDagNode { +export function normaliseDagNode (node: dagNode | PBNode, cidStr: string): NormalizedDagNode { const code = getCodeOrNull(cidStr) if (isDagPbNode(node, cidStr)) { return normaliseDagPb(node, cidStr, dagPb.code) @@ -34,6 +34,7 @@ export default function normaliseDagNode (node: dagNode | PBNode, cidStr: string // @ts-expect-error - todo: resolve node type error return normaliseDagCbor(node, cidStr, code ?? dagCbor.code) } +export default normaliseDagNode /** * Normalize links and add type info. Add unixfs info where available diff --git a/src/lib/normalize-dag-node.test.ts b/src/lib/normalize-dag-node.test.ts index a4351332..acc79795 100644 --- a/src/lib/normalize-dag-node.test.ts +++ b/src/lib/normalize-dag-node.test.ts @@ -1,72 +1,182 @@ /* global it expect */ import * as dagCbor from '@ipld/dag-cbor' +import * as dagPb from '@ipld/dag-pb' import { CID } from 'multiformats' -import normaliseDagNode from './normalise-dag-node' +import { normaliseDagNode, normaliseDagPb, findAndReplaceDagCborLinks } from './normalise-dag-node' const cid1 = 'bafyreifiioc5v7xh3xbqjkdvgcz5ywo2vnhzd2gdnybfogxajjnchzzhei' const cid2 = 'bafyreigej5njyhiye4rlhntifea6uwzkuwhkxvm2nxyyufnedzqqhhokpi' const cid3 = 'bafyreidyyt24wtr7q5plglwroysqzn3ph42nvna4iswllnha7xrwogme3q' +const cid4 = 'bafyrwigbexamue2ba3hmtai7hwlcmd6ekiqsduyf5avv7oz6ln3radvjde' -it('normalizes a simple cbor node', () => { - const obj: any = { foo: 'bar' } - const res = normaliseDagNode(obj, cid1) +describe('normaliseDagNode', () => { + it('normalizes a simple cbor node', () => { + const obj: any = { foo: 'bar' } + const res = normaliseDagNode(obj, cid1) - expect(res).toEqual(expect.objectContaining({ - cid: cid1, - data: obj, - type: dagCbor.code, - links: [] - })) -}) + expect(res).toEqual(expect.objectContaining({ + cid: cid1, + data: obj, + type: dagCbor.code, + links: [] + })) + }) + + it('normalizes a cbor node with an empty array', () => { + const obj: any = { foo: [] } + const res = normaliseDagNode(obj, cid1) + + expect(res).toEqual(expect.objectContaining({ + cid: cid1, + data: obj, + type: dagCbor.code, + links: [] + })) + }) + + it('normalizes a cbor node with links', () => { + const obj: any = { + foo: CID.parse(cid2), + bar: [CID.parse(cid2), CID.parse(cid3)] + } -it('normalizes a cbor node with an empty array', () => { - const obj: any = { foo: [] } - const res = normaliseDagNode(obj, cid1) + const res = normaliseDagNode(obj, cid1) + + expect(res).toEqual({ + cid: cid1, + data: obj, + type: dagCbor.code, + format: 'unknown', + size: BigInt(0), + links: [ + { + path: 'foo', + source: cid1, + target: cid2, + index: 0, + size: BigInt(0) + }, + { + path: 'bar/0', + source: cid1, + target: cid2, + index: 0, + size: BigInt(0) + }, + { + path: 'bar/1', + source: cid1, + target: cid3, + index: 0, + size: BigInt(0) + } + ] + }) + }) +}) - expect(res).toEqual(expect.objectContaining({ - cid: cid1, - data: obj, - type: dagCbor.code, - links: [] - })) +describe('normaliseDagPb', () => { + it('throws an error when cidStr is null', () => { + const node = { + Data: new Uint8Array(), + Links: [] + } + expect(() => normaliseDagPb(node, 'invalidCid', dagPb.code)).toThrow('cidStr is null for cid: invalidCid') + }) }) -it('normalizes a cbor node with links', () => { - const obj: any = { - foo: CID.parse(cid2), - bar: [CID.parse(cid2), CID.parse(cid3)] - } - - const res = normaliseDagNode(obj, cid1) - - expect(res).toEqual({ - cid: cid1, - data: obj, - type: dagCbor.code, - format: 'unknown', - size: BigInt(0), - links: [ - { - path: 'foo', - source: cid1, - target: cid2, - index: 0, - size: BigInt(0) - }, - { - path: 'bar/0', - source: cid1, - target: cid2, - index: 0, - size: BigInt(0) - }, - { - path: 'bar/1', - source: cid1, - target: cid3, - index: 0, - size: BigInt(0) - } +describe('findAndReplaceDagCborLinks', () => { + /** + * Note that the sourceCid's content does not matter for finding links, so these tests are all made up values + */ + const sourceCid = cid1 + + it('should return an empty array for null input', () => { + const result = findAndReplaceDagCborLinks(null, sourceCid) + expect(result).toStrictEqual([]) + }) + + it('should return an empty array for non-object input', () => { + const result = findAndReplaceDagCborLinks('notAnObject', sourceCid) + expect(result).toStrictEqual([]) + }) + + it('should return an empty array for an empty object', () => { + const result = findAndReplaceDagCborLinks({}, sourceCid) + expect(result).toStrictEqual([]) + }) + + it('should return an empty array for an object without "/" property', () => { + const result = findAndReplaceDagCborLinks({ someKey: 'someValue' }, sourceCid) + expect(result).toStrictEqual([]) + }) + + it('should return a NormalizedDagLink for an object with "/" property containing a valid CID', () => { + const input = { '/': cid2 } + const result = findAndReplaceDagCborLinks(input, sourceCid) + expect(result).toStrictEqual([ + { path: '', source: sourceCid, target: cid2, size: BigInt(0), index: 0 } + ]) + }) + + it('should replace "/" property with CID string in a valid DAG-CBOR link object', () => { + const input = { '/': cid2 } + const result = findAndReplaceDagCborLinks(input, sourceCid) + expect(result).toEqual([{ + index: 0, + path: '', + size: BigInt(0), + source: sourceCid, + target: cid2 + }]) + }) + + it('should handle nested arrays and return appropriate links', () => { + const input = [{ '/': cid2 }, { someKey: 'value' }] + const result = findAndReplaceDagCborLinks(input, sourceCid) + expect(result).toStrictEqual([ + { path: '/0', source: sourceCid, target: cid2, size: BigInt(0), index: 0 } + ]) + }) + + it('should handle deeply nested objects with "/" property', () => { + const input = { nested: { '/': cid2 } } + const result = findAndReplaceDagCborLinks(input, sourceCid) + expect(result).toStrictEqual([ + { path: 'nested', source: sourceCid, target: cid2, size: BigInt(0), index: 0 } + ]) + }) + + it('should return an empty array if the "/" property does not contain a valid CID', () => { + const input = { '/': 'invalidCid' } + const result = findAndReplaceDagCborLinks(input, sourceCid) + expect(result).toStrictEqual([]) + }) + + it('should process nested arrays correctly and return all valid links', () => { + const input = [ + { '/': cid2 }, + [{ '/': cid3 }, { '/': 'invalidCid' }], + { '/': cid4 } ] + const result = findAndReplaceDagCborLinks(input, sourceCid) + expect(result).toStrictEqual([ + { path: '/0', source: sourceCid, target: cid2, size: BigInt(0), index: 0 }, + { path: '/1/0', source: sourceCid, target: cid3, size: BigInt(0), index: 0 }, + { path: '/2', source: sourceCid, target: cid4, size: BigInt(0), index: 0 } + ]) + }) + + it('should handle complex nested structures and only replace valid CID links', () => { + const input = { + array: [{ '/': cid2 }, { nestedArray: [{ '/': cid3 }] }], + object: { '/': cid4 } + } + const result = findAndReplaceDagCborLinks(input, sourceCid) + expect(result).toStrictEqual([ + { path: 'array/0', source: sourceCid, target: cid2, size: BigInt(0), index: 0 }, + { path: 'array/1/nestedArray/0', source: sourceCid, target: cid3, size: BigInt(0), index: 0 }, + { path: 'object', source: sourceCid, target: cid4, size: BigInt(0), index: 0 } + ]) }) }) diff --git a/vitest.config.js b/vitest.config.js index d77914bf..1659246e 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -10,12 +10,31 @@ export default defineConfig((configEnv) => mergeConfig( setupFiles: './test/unit/setup.ts', include: [ 'src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}' - ] - // deps: { - // inline: [ - // 'ipld-explorer-components' - // ] - // } + ], + coverage: { + exclude: [ + 'coverage/**', + 'dist/**', + '**/node_modules/**', + '**/[.]**', + 'packages/*/test?(s)/**', + '**/*.d.ts', + '**/virtual:*', + '**/__x00__*', + '**/\x00*', + 'cypress/**', + 'test?(s)/**', + 'test?(-*).?(c|m)[jt]s?(x)', + '**/*{.,-}{test,spec,bench,benchmark}?(-d).?(c|m)[jt]s?(x)', + '**/__tests__/**', + '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*', + '**/vitest.{workspace,projects}.[jt]s?(on)', + '**/.{eslint,mocha,prettier}rc.{?(c|m)js,yml}', + 'storybook-static/**', + 'build/**', + 'dev/**' + ] + } } }) )) From d68cf24c50dc5cd2d48d4be1d4eff96f12ba39bc Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:18:31 -0500 Subject: [PATCH 2/4] feat: remove dependency on Buffer --- .aegir.js | 1 + .storybook/preview.ts | 3 --- dev/devPage.tsx | 4 ---- src/lib/browser-shims.js | 7 ------ src/lib/extract-info.ts | 5 ++++- src/lib/normalise-dag-node.ts | 35 ++++++++++++++++++++++-------- src/lib/normalize-dag-node.test.ts | 12 ++++++---- vite.config.ts | 1 - 8 files changed, 39 insertions(+), 29 deletions(-) delete mode 100644 src/lib/browser-shims.js diff --git a/.aegir.js b/.aegir.js index 5592bb3c..cf649164 100644 --- a/.aegir.js +++ b/.aegir.js @@ -105,6 +105,7 @@ export default { // vite stuff 'rollup-plugin-node-polyfills', + '@vitest/coverage-v8', // typescript plugins 'typescript-plugin-css-modules' diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 0ce56e9a..fa3b7dd7 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,7 +1,4 @@ import { type Preview } from '@storybook/react'; -import { Buffer } from 'buffer' - -globalThis.Buffer = Buffer // import CSS files import 'ipfs-css' diff --git a/dev/devPage.tsx b/dev/devPage.tsx index 1a06fdef..38c01b5e 100644 --- a/dev/devPage.tsx +++ b/dev/devPage.tsx @@ -1,6 +1,4 @@ -/* globals globalThis */ import 'ipfs-css' -import { Buffer } from 'buffer' import React, { type MouseEvent, useEffect, useState } from 'react' import { createRoot } from 'react-dom/client' import { I18nextProvider, useTranslation } from 'react-i18next' @@ -8,8 +6,6 @@ import 'tachyons' import i18n from '../src/i18n.js' import { ExplorePage, StartExploringPage, IpldExploreForm, IpldCarExploreForm, ExploreProvider, HeliaProvider, useExplore, useHelia } from '../src/index.js' -globalThis.Buffer = globalThis.Buffer ?? Buffer - const HeaderComponent: React.FC = () => { const activeColor = 'navy 0-100' const inActiveColor = 'navy o-50' diff --git a/src/lib/browser-shims.js b/src/lib/browser-shims.js deleted file mode 100644 index 2c5f7255..00000000 --- a/src/lib/browser-shims.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Output any shims/polyfills here - */ - -import { Buffer } from 'rollup-plugin-node-polyfills/polyfills/buffer-es6' - -export { Buffer } diff --git a/src/lib/extract-info.ts b/src/lib/extract-info.ts index 527785b6..3e49c76a 100644 --- a/src/lib/extract-info.ts +++ b/src/lib/extract-info.ts @@ -2,7 +2,10 @@ import { type CID } from 'multiformats' import { decodeCid } from '../components/cid-info/decode-cid.js' import getCodecNameFromCode from './get-codec-name-from-code' -const toHex = (bytes: Uint8Array): string => Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('hex').toUpperCase() +const toHex = (bytes: Uint8Array): string => Array.from(new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)) + .map(byte => byte.toString(16).padStart(2, '0')) + .join('') + .toUpperCase() export interface ExtractedInfo { base: string diff --git a/src/lib/normalise-dag-node.ts b/src/lib/normalise-dag-node.ts index d9e299f4..54fdd79c 100644 --- a/src/lib/normalise-dag-node.ts +++ b/src/lib/normalise-dag-node.ts @@ -4,7 +4,7 @@ import * as dagPb from '@ipld/dag-pb' import { UnixFS } from 'ipfs-unixfs' import { toCidOrNull, getCodeOrNull, toCidStrOrNull } from './cid.js' import { isTruthy } from './helpers.js' -import type { NormalizedDagPbNodeFormat, CodecType, NormalizedDagNode, NormalizedDagLink, dagNode } from '../types.js' +import type { NormalizedDagPbNodeFormat, CodecType, NormalizedDagNode, NormalizedDagLink, dagNode, CID } from '../types.js' import type { PBLink, PBNode } from '@ipld/dag-pb' function isDagPbNode (node: dagNode | PBNode, cid: string): node is PBNode { @@ -121,16 +121,36 @@ export function normaliseDagCbor (data: NormalizedDagNode['data'], cid: string, } } +type PlainObjectOrArray = Record | unknown[] | string + +function isPlainObjectOrArray (obj: unknown): obj is PlainObjectOrArray { + return ( + obj !== null && + typeof obj === 'object' && + !(obj instanceof ArrayBuffer) && + !ArrayBuffer.isView(obj) && + !(typeof obj === 'string') + ) +} + +type DagCborNodeObject = Record & { '/': string | CID | null } + +/** + * This should be called after `isPlainObjectOrArray` to avoid type errors. + */ +function isDagCborNodeObject (obj: PlainObjectOrArray): obj is DagCborNodeObject { + return Object.keys(obj).length === 1 && (obj as Record)['/'] != null +} + /** * A valid IPLD link in a dag-cbor node is an object with single "/" property. */ export function findAndReplaceDagCborLinks (obj: unknown, sourceCid: string, path: string = ''): NormalizedDagLink[] { - if (obj == null || typeof obj !== 'object' || Buffer.isBuffer(obj) || typeof obj === 'string') { + if (!isPlainObjectOrArray(obj)) { return [] } - // FIXME: remove as any cast - const cid = toCidOrNull(obj as any) + const cid = toCidOrNull(obj as string) if (cid != null) { return [{ path, source: sourceCid, target: cid.toString(), size: BigInt(0), index: 0 }] } @@ -147,14 +167,12 @@ export function findAndReplaceDagCborLinks (obj: unknown, sourceCid: string, pat const keys = Object.keys(obj) // Support older `{ "/": Buffer } style links until all the IPLD formats are updated. - if (keys.length === 1 && keys[0] === '/') { - // @ts-expect-error - todo: resolve this type error + if (isDagCborNodeObject(obj)) { const targetCid = toCidOrNull(obj['/']) if (targetCid == null) return [] const target = targetCid.toString() - // @ts-expect-error - todo: resolve this type error obj['/'] = target return [{ path, source: sourceCid, target, size: BigInt(0), index: 0 }] @@ -162,8 +180,7 @@ export function findAndReplaceDagCborLinks (obj: unknown, sourceCid: string, pat if (keys.length > 0) { return keys - // @ts-expect-error - todo: resolve this type error - .map(key => findAndReplaceDagCborLinks(obj[key], sourceCid, isTruthy(path) ? `${path}/${key}` : `${key}`)) + .map(key => findAndReplaceDagCborLinks((obj as Record)[key], sourceCid, isTruthy(path) ? `${path}/${key}` : `${key}`)) .reduce((a, b) => a.concat(b)) .filter(a => Boolean(a)) } else { diff --git a/src/lib/normalize-dag-node.test.ts b/src/lib/normalize-dag-node.test.ts index acc79795..a1ad88ff 100644 --- a/src/lib/normalize-dag-node.test.ts +++ b/src/lib/normalize-dag-node.test.ts @@ -97,13 +97,17 @@ describe('findAndReplaceDagCborLinks', () => { }) it('should return an empty array for non-object input', () => { - const result = findAndReplaceDagCborLinks('notAnObject', sourceCid) - expect(result).toStrictEqual([]) + expect(findAndReplaceDagCborLinks('notAnObject', sourceCid)).toStrictEqual([]) }) it('should return an empty array for an empty object', () => { - const result = findAndReplaceDagCborLinks({}, sourceCid) - expect(result).toStrictEqual([]) + expect(findAndReplaceDagCborLinks({}, sourceCid)).toStrictEqual([]) + }) + + it('should return an empty array for a buffer-like object', () => { + expect(findAndReplaceDagCborLinks(Buffer.from('some data'), sourceCid)).toStrictEqual([]) + expect(findAndReplaceDagCborLinks(new Uint8Array([1, 2, 3]), sourceCid)).toStrictEqual([]) + expect(findAndReplaceDagCborLinks(new ArrayBuffer(8), sourceCid)).toStrictEqual([]) }) it('should return an empty array for an object without "/" property', () => { diff --git a/vite.config.ts b/vite.config.ts index 1f604b67..a1553579 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -93,7 +93,6 @@ export default defineConfig(({ mode, command }) => { { find: /^stream$/, replacement: 'rollup-plugin-node-polyfills/polyfills/stream' }, { find: /^_stream_duplex$/, replacement: 'rollup-plugin-node-polyfills/polyfills/readable-stream/duplex' }, { find: /^_stream_transform$/, replacement: 'rollup-plugin-node-polyfills/polyfills/readable-stream/transform' }, - { find: /^buffer$/, replacement: 'rollup-plugin-node-polyfills/polyfills/buffer-es6' }, ] } viteOptimizeDeps.include = [] From 26636102759a1ec3c0323c5a3554745d742a25fb Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:19:16 -0500 Subject: [PATCH 3/4] fix: setExplorePath handles url check properly --- src/providers/explore.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/providers/explore.tsx b/src/providers/explore.tsx index 24ebf52c..bf62533e 100644 --- a/src/providers/explore.tsx +++ b/src/providers/explore.tsx @@ -151,10 +151,10 @@ export const ExploreProvider = ({ children, state, explorePathPrefix = '#/explor }, [helia, path]) const setExplorePath = (path: string | null): void => { - const newPath = processPath(path, explorePathPrefix) - if (newPath != null && !window.location.href.includes(encodeURI(newPath))) { + if (path != null && !window.location.href.includes(path)) { throw new Error('setExplorePath should only be used to update the state, not the URL. If you are using a routing library that doesn\'t allow you to listen to hashchange events, ensure the URL is updated prior to calling setExplorePath.') } + const newPath = processPath(path, explorePathPrefix) setExploreState((exploreState) => ({ ...exploreState, path: newPath From a56b9d5113a68abc7b18f4231a585394795b08e2 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:44:38 -0500 Subject: [PATCH 4/4] fix: more type safety --- src/lib/cid.ts | 2 +- src/lib/normalise-dag-node.ts | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/cid.ts b/src/lib/cid.ts index 65703569..3830a7fb 100644 --- a/src/lib/cid.ts +++ b/src/lib/cid.ts @@ -6,7 +6,7 @@ import { CID, type MultibaseDecoder } from 'multiformats/cid' /** * Converts a value to a CID or returns null if it cannot be converted. */ -export function toCidOrNull (value: CID | string | null, base?: MultibaseDecoder | undefined): CID | null { +export function toCidOrNull (value: CID | string | null | unknown, base?: MultibaseDecoder | undefined): CID | null { if (value == null) return null try { return CID.asCID(value) ?? CID.parse(value as string, base) diff --git a/src/lib/normalise-dag-node.ts b/src/lib/normalise-dag-node.ts index 54fdd79c..2b19a581 100644 --- a/src/lib/normalise-dag-node.ts +++ b/src/lib/normalise-dag-node.ts @@ -2,9 +2,10 @@ import * as dagCbor from '@ipld/dag-cbor' import * as dagPb from '@ipld/dag-pb' import { UnixFS } from 'ipfs-unixfs' +import { type CID } from 'multiformats/cid' import { toCidOrNull, getCodeOrNull, toCidStrOrNull } from './cid.js' import { isTruthy } from './helpers.js' -import type { NormalizedDagPbNodeFormat, CodecType, NormalizedDagNode, NormalizedDagLink, dagNode, CID } from '../types.js' +import type { NormalizedDagPbNodeFormat, CodecType, NormalizedDagNode, NormalizedDagLink, dagNode } from '../types.js' import type { PBLink, PBNode } from '@ipld/dag-pb' function isDagPbNode (node: dagNode | PBNode, cid: string): node is PBNode { @@ -150,9 +151,12 @@ export function findAndReplaceDagCborLinks (obj: unknown, sourceCid: string, pat return [] } - const cid = toCidOrNull(obj as string) - if (cid != null) { - return [{ path, source: sourceCid, target: cid.toString(), size: BigInt(0), index: 0 }] + const cid = toCidOrNull(obj) + if (typeof obj === 'string' || cid != null) { + if (cid != null) { + return [{ path, source: sourceCid, target: cid.toString(), size: BigInt(0), index: 0 }] + } + return [] } if (Array.isArray(obj)) { @@ -180,7 +184,7 @@ export function findAndReplaceDagCborLinks (obj: unknown, sourceCid: string, pat if (keys.length > 0) { return keys - .map(key => findAndReplaceDagCborLinks((obj as Record)[key], sourceCid, isTruthy(path) ? `${path}/${key}` : `${key}`)) + .map(key => findAndReplaceDagCborLinks(obj[key], sourceCid, isTruthy(path) ? `${path}/${key}` : `${key}`)) .reduce((a, b) => a.concat(b)) .filter(a => Boolean(a)) } else {