Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/setup-deno/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ runs:
steps:
- uses: denoland/setup-deno@v2
with:
deno-version: 2.7.6 # Keep in sync with mise.toml
deno-version: 2.7.7 # Keep in sync with mise.toml
20 changes: 20 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ Version 2.0.7

To be released.

### @fedify/fedify

- Switched Fedify's JSR-facing JSON-LD imports to jsonld's ESM entrypoint
so source-based runtimes such as Deno, Cloudflare Workers, and Fresh 2's
Vite SSR pipeline do not have to evaluate the package through CommonJS
interop when loading Linked Data signature support. Fresh 2 development
mode has been verified on Deno 2.7.7 after an upstream Deno 2.7.6 dev
server regression was fixed. [[#621], [#639]]

[#621]: https://github.com/fedify-dev/fedify/issues/621
[#639]: https://github.com/fedify-dev/fedify/pull/639

### @fedify/vocab-runtime

- Fixed multibase public key handling to stop relying on the deprecated
CommonJS-only `multicodec` package. This removes the Vite SSR crash that
prevented Fresh 2 applications from importing `@fedify/fedify` with
`TypeError: varint.encode is not a function`. Fresh 2 no longer needs a
Vite externalization workaround for Fedify. [[#621], [#639]]

### @fedify/init

- Revived removed `fedify init` options. [[#632], [#638] by ChanHaeng Lee]
Expand Down
233 changes: 105 additions & 128 deletions deno.lock

Large diffs are not rendered by default.

28 changes: 4 additions & 24 deletions docs/manual/integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -795,30 +795,10 @@ deno add jsr:@fedify/fresh
> [!NOTE]
> The `@fedify/fresh` package only supports Fresh 2.x.

> [!WARNING]
> Due to `@fedify/fedify`'s `multicodec` dependency CJS issue, you should
> externalize `@fedify/fedify` in `vite.config.ts`.
>
> ~~~~ typescript
> import { fresh } from "@fresh/plugin-vite";
> import { defineConfig } from "vite";
>
> export default defineConfig({
> plugins: [fresh()],
> ssr: {
> external: [
> "@fedify/fedify",
> ],
> },
> build: {
> rollupOptions: {
> external: [
> "@fedify/fedify",
> ],
> },
> },
> });
> ~~~~
> [!NOTE]
> Fresh 2 development mode has been verified with Deno 2.7.7. Deno 2.7.6 had
> an upstream Vite/esbuild regression that caused
> `Callback called multiple times` before Fedify code could run.

> [!WARNING]
> Due to `@fedify/fedify` use `Temporal` inside, you should add `deno.unstable`
Expand Down
6 changes: 5 additions & 1 deletion examples/fresh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Fedify–Fresh integration example
This is a simple example of how to integrate Fedify into an [Fresh]
application.

Use Deno 2.7.7 or later. Deno 2.7.6 had an upstream Fresh/Vite dev-server
regression that caused `Callback called multiple times` errors before Fedify
code could run.

[Fresh]: https://fresh.deno.dev/


Expand All @@ -26,7 +30,7 @@ Running the example
3. Move to example folder

~~~~ sh
cd ../../examples/fresh
cd examples/fresh
~~~~

4. Start the server:
Expand Down
43 changes: 26 additions & 17 deletions examples/fresh/federation.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import { createFederation, MemoryKvStore } from "@fedify/fedify";
import { Person } from "@fedify/vocab";

// Create the federation instance
export const federation = createFederation<void>({
kv: new MemoryKvStore(),
});

// Set up a NodeInfo dispatcher for the federation instance
federation
.setNodeInfoDispatcher("/nodeinfo/2.1", () => {
return {
software: {
name: "fresh-example", // Lowercase, digits, and hyphens only.
version: "1.0.0",
homepage: new URL("https://your-software.com/"),
},
protocols: ["activitypub"],
usage: {
// Usage statistics is hard-coded here for demonstration purposes.
// You should replace these with real statistics:
users: { total: 100, activeHalfyear: 50, activeMonth: 20 },
localPosts: 1000,
localComments: 2000,
},
};
federation.setNodeInfoDispatcher("/nodeinfo/2.1", () => {
return {
software: {
name: "fresh-example", // Lowercase, digits, and hyphens only.
version: "1.0.0",
homepage: new URL("https://your-software.com/"),
},
protocols: ["activitypub"],
usage: {
// Usage statistics is hard-coded here for demonstration purposes.
// You should replace these with real statistics:
users: { total: 100, activeHalfyear: 50, activeMonth: 20 },
localPosts: 1000,
localComments: 2000,
},
};
});

federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
if (identifier !== "sample") return null;
return new Person({
id: ctx.getActorUri(identifier),
name: "Sample",
preferredUsername: identifier,
});
});
12 changes: 0 additions & 12 deletions examples/fresh/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,4 @@ import { defineConfig } from "vite";

export default defineConfig({
plugins: [fresh()],
ssr: {
external: [
"@fedify/fedify",
],
},
build: {
rollupOptions: {
external: [
"@fedify/fedify",
],
},
},
});
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tools]
bun = "1.2.22"
deno = "2.7.6"
deno = "2.7.7"
node = "22"
pnpm = "10.28.0"

Expand Down
1 change: 0 additions & 1 deletion packages/fedify/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"fetch-mock": "npm:fetch-mock@^12.5.2",
"json-canon": "npm:json-canon@^1.0.1",
"jsonld": "npm:jsonld@^9.0.0",
"multicodec": "npm:multicodec@^3.2.1",
"pkijs": "npm:pkijs@^3.3.3",
"structured-field-values": "npm:structured-field-values@^2.0.4",
"uri-template-router": "npm:uri-template-router@^1.0.0",
Expand Down
1 change: 0 additions & 1 deletion packages/fedify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@
"es-toolkit": "catalog:",
"json-canon": "^1.0.1",
"jsonld": "^9.0.0",
"multicodec": "^3.2.1",
"structured-field-values": "^2.0.4",
"uri-template-router": "^1.0.0",
"url-template": "^3.1.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/fedify/src/sig/ld.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { getLogger } from "@logtape/logtape";
import { SpanStatusCode, trace, type TracerProvider } from "@opentelemetry/api";
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
import { encodeHex } from "byte-encodings/hex";
import jsonld from "jsonld";
// @ts-ignore TS7016
import jsonld from "jsonld/dist/jsonld.esm.js";
Comment thread
dahlia marked this conversation as resolved.
Outdated
import metadata from "../../deno.json" with { type: "json" };
import { fetchKey, type KeyCache, validateCryptoKey } from "./key.ts";

Expand Down
1 change: 0 additions & 1 deletion packages/vocab-runtime/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"asn1js": "npm:asn1js@^3.0.6",
"byte-encodings": "npm:byte-encodings@^1.0.11",
"fetch-mock": "npm:fetch-mock@^12.5.4",
"multicodec": "npm:multicodec@^3.2.1",
"pkijs": "npm:pkijs@^3.2.5"
},
"exclude": [
Expand Down
1 change: 0 additions & 1 deletion packages/vocab-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
"@opentelemetry/api": "catalog:",
"asn1js": "catalog:",
"byte-encodings": "catalog:",
"multicodec": "^3.2.1",
"pkijs": "catalog:"
}
}
68 changes: 68 additions & 0 deletions packages/vocab-runtime/src/internal/multicodec.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { deepStrictEqual, throws } from "node:assert";
import { test } from "node:test";
import {
addMulticodecPrefix,
getMulticodecPrefix,
removeMulticodecPrefix,
} from "./multicodec.ts";

test("getMulticodecPrefix() decodes supported multicodec prefixes", () => {
deepStrictEqual(
getMulticodecPrefix(Uint8Array.from([0xed, 0x01, 0xaa])),
{ code: 0xed, prefixLength: 2 },
);
deepStrictEqual(
getMulticodecPrefix(Uint8Array.from([0x85, 0x24, 0xaa])),
{ code: 0x1205, prefixLength: 2 },
);
});

test("removeMulticodecPrefix() strips the varint prefix", () => {
deepStrictEqual(
removeMulticodecPrefix(Uint8Array.from([0xed, 0x01, 0x11, 0x22])),
Uint8Array.from([0x11, 0x22]),
);
});

test("addMulticodecPrefix() prepends the varint-encoded code", () => {
deepStrictEqual(
addMulticodecPrefix(0xed, Uint8Array.from([0x11, 0x22])),
Uint8Array.from([0xed, 0x01, 0x11, 0x22]),
);
deepStrictEqual(
addMulticodecPrefix(0x1205, Uint8Array.from([0x11, 0x22])),
Uint8Array.from([0x85, 0x24, 0x11, 0x22]),
);
});

test("multicodec helpers round-trip prefixed payloads", () => {
const payload = Uint8Array.from([0xde, 0xad, 0xbe, 0xef]);
const prefixed = addMulticodecPrefix(0x1205, payload);
deepStrictEqual(getMulticodecPrefix(prefixed), {
code: 0x1205,
prefixLength: 2,
});
deepStrictEqual(removeMulticodecPrefix(prefixed), payload);
});

test("multicodec helpers reject malformed prefixes", () => {
throws(
() => getMulticodecPrefix(new Uint8Array([])),
new TypeError("Invalid multicodec prefix."),
);
throws(
() => getMulticodecPrefix(Uint8Array.from([0x80])),
new TypeError("Invalid multicodec prefix."),
);
throws(
() =>
getMulticodecPrefix(
Uint8Array.from([0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80]),
),
new TypeError("Invalid multicodec prefix."),
);
throws(
() => addMulticodecPrefix(-1, Uint8Array.from([0x00])),
new TypeError("Invalid multicodec code."),
);
});
53 changes: 53 additions & 0 deletions packages/vocab-runtime/src/internal/multicodec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const INVALID_MULTICODEC_PREFIX = "Invalid multicodec prefix.";

export interface MulticodecPrefix {
code: number;
prefixLength: number;
}

export function getMulticodecPrefix(
data: Uint8Array,
): MulticodecPrefix {
if (data.length < 1) throw new TypeError(INVALID_MULTICODEC_PREFIX);
let code = 0;
let shift = 0;
for (let i = 0; i < data.length; i++) {
const byte = data[i];
code += (byte & 0x7f) * 2 ** shift;
if (code > Number.MAX_SAFE_INTEGER) {
throw new TypeError(INVALID_MULTICODEC_PREFIX);
}
if ((byte & 0x80) === 0) {
return { code, prefixLength: i + 1 };
}
shift += 7;
if (shift >= 53) throw new TypeError(INVALID_MULTICODEC_PREFIX);
}
throw new TypeError(INVALID_MULTICODEC_PREFIX);
}

export function removeMulticodecPrefix(data: Uint8Array): Uint8Array {
const { prefixLength } = getMulticodecPrefix(data);
return data.slice(prefixLength);
}

export function addMulticodecPrefix(
code: number,
payload: Uint8Array,
): Uint8Array {
if (!Number.isSafeInteger(code) || code < 0) {
throw new TypeError("Invalid multicodec code.");
}
const prefix: number[] = [];
let value = code;
do {
let byte = value & 0x7f;
value = Math.floor(value / 0x80);
if (value > 0) byte |= 0x80;
prefix.push(byte);
} while (value > 0);
const prefixed = new Uint8Array(prefix.length + payload.length);
prefixed.set(prefix);
prefixed.set(payload, prefix.length);
return prefixed;
}
23 changes: 22 additions & 1 deletion packages/vocab-runtime/src/key.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { deepStrictEqual } from "node:assert";
import { deepStrictEqual, rejects } from "node:assert";
import { test } from "node:test";
import { exportJwk, importJwk } from "./jwk.ts";
import {
Expand All @@ -9,6 +9,7 @@ import {
importPkcs1,
importSpki,
} from "./key.ts";
import { encodeMultibase } from "./multibase/mod.ts";

// cSpell: disable
const rsaSpki = "-----BEGIN PUBLIC KEY-----\n" +
Expand Down Expand Up @@ -122,6 +123,26 @@ test("importMultibase()", async () => {
deepStrictEqual(await exportJwk(ed25519Key), ed25519Jwk);
});

test("importMultibase() rejects malformed multicodec prefixes", async () => {
const decoder = new TextDecoder();
await rejects(
() =>
importMultibaseKey(
decoder.decode(encodeMultibase("base58btc", new Uint8Array([]))),
),
new TypeError("Invalid multicodec prefix."),
);
await rejects(
() =>
importMultibaseKey(
decoder.decode(
encodeMultibase("base58btc", Uint8Array.from([0x80])),
),
),
new TypeError("Invalid multicodec prefix."),
);
});

test("exportMultibaseKey()", async () => {
const rsaKey = await importJwk(rsaJwk, "public");
const rsaMb = await exportMultibaseKey(rsaKey);
Expand Down
Loading
Loading