From 5a4123f6b46d46b5807026ebe4aaa73f14eebf22 Mon Sep 17 00:00:00 2001 From: Benjamin Goering <171782+gobengo@users.noreply.github.com> Date: Tue, 4 Apr 2023 13:56:14 -0700 Subject: [PATCH] feat!: add did mailto package, replacing `createDidMailtoFromEmail` (#722) Motivation: * #682 Breaking Changes * remove `createDidMailtoFromEmail` export from `@web3-storage/access/agent` https://github.com/web3-storage/w3up/pull/722/commits/93e29c6afd0a0ec7a0eb3997ea1197533f7cff3a#diff-69a4efe733b2d7920dc103a0370eb1a285403e39c41e1b5e9a0718ea66b5a32fL33 * no dependencies under our org: https://github.com/search?q=org%3Aweb3-storage%20createDidMailtoFromEmail&type=code * Motivation: encouragement [from @Gozala](https://github.com/web3-storage/w3up/pull/722#discussion_r1156572341) and [@travis](https://github.com/web3-storage/w3up/pull/722#discussion_r1157552497) in review --- .github/release-please-config.json | 1 + .github/workflows/did-mailto.yml | 37 ++++++++ .github/workflows/release.yml | 1 + packages/access-api/package.json | 1 + .../access-api/src/routes/validate-email.js | 4 +- .../src/service/access-authorize.js | 4 +- packages/access-api/src/utils/did-mailto.js | 12 --- .../access-api/test/access-authorize.test.js | 4 +- packages/access-api/test/access-claim.test.js | 4 +- .../test/access-client-agent.test.js | 10 +-- .../access-api/test/access-delegate.test.js | 8 +- packages/access-api/test/provider-add.test.js | 2 +- packages/access-api/test/provisions.test.js | 2 +- packages/access-api/tsconfig.json | 6 +- packages/access-client/package.json | 1 + packages/access-client/src/agent-use-cases.js | 11 +-- packages/access-client/src/agent.js | 3 +- .../access-client/src/utils/did-mailto.js | 15 ---- packages/access-client/test/agent.test.js | 9 +- packages/access-client/tsconfig.json | 2 +- packages/did-mailto/package.json | 67 ++++++++++++++ packages/did-mailto/src/index.js | 74 ++++++++++++++++ packages/did-mailto/src/types.ts | 12 +++ packages/did-mailto/test/did-mailto.spec.js | 88 +++++++++++++++++++ packages/did-mailto/test/test-types.ts | 5 ++ packages/did-mailto/tsconfig.json | 9 ++ pnpm-lock.yaml | 16 ++++ 27 files changed, 346 insertions(+), 62 deletions(-) create mode 100644 .github/workflows/did-mailto.yml delete mode 100644 packages/access-api/src/utils/did-mailto.js delete mode 100644 packages/access-client/src/utils/did-mailto.js create mode 100644 packages/did-mailto/package.json create mode 100644 packages/did-mailto/src/index.js create mode 100644 packages/did-mailto/src/types.ts create mode 100644 packages/did-mailto/test/did-mailto.spec.js create mode 100644 packages/did-mailto/test/test-types.ts create mode 100644 packages/did-mailto/tsconfig.json diff --git a/.github/release-please-config.json b/.github/release-please-config.json index a65c8870e..2ece5dad8 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -5,6 +5,7 @@ "packages/access-client": {}, "packages/access-api": {}, "packages/capabilities": {}, + "packages/did-mailto": {}, "packages/upload-api": {}, "packages/upload-client": {}, "packages/w3up-client": {} diff --git a/.github/workflows/did-mailto.yml b/.github/workflows/did-mailto.yml new file mode 100644 index 000000000..b82464464 --- /dev/null +++ b/.github/workflows/did-mailto.yml @@ -0,0 +1,37 @@ +name: did-mailto +on: + push: + branches: + - main + paths: + - 'packages/did-mailto/**' + - '.github/workflows/did-mailto.yml' + - 'pnpm-lock.yaml' + pull_request: + paths: + - 'packages/did-mailto/**' + - '.github/workflows/did-mailto.yml' + - 'pnpm-lock.yaml' +jobs: + test: + name: Test + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./packages/did-mailto + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install + uses: pnpm/action-setup@v2.2.3 + with: + version: 7 + - name: Setup + uses: actions/setup-node@v3 + with: + node-version: 18 + registry-url: https://registry.npmjs.org/ + cache: 'pnpm' + - run: pnpm --filter '@web3-storage/did-mailto...' install + - run: pnpm --filter '@web3-storage/did-mailto' lint + - run: pnpm --filter '@web3-storage/did-mailto' test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 842009d71..f3d7ee303 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,6 +34,7 @@ jobs: if: | contains(fromJson(needs.release.outputs.paths_released), 'packages/access-client') || contains(fromJson(needs.release.outputs.paths_released), 'packages/capabilities') || + contains(fromJson(needs.release.outputs.paths_released), 'packages/did-mailto') || contains(fromJson(needs.release.outputs.paths_released), 'packages/upload-client') || contains(fromJson(needs.release.outputs.paths_released), 'packages/upload-api') || contains(fromJson(needs.release.outputs.paths_released), 'packages/w3up-client') diff --git a/packages/access-api/package.json b/packages/access-api/package.json index 61dae28ce..eada063e3 100644 --- a/packages/access-api/package.json +++ b/packages/access-api/package.json @@ -27,6 +27,7 @@ "@ucanto/validator": "^6.1.0", "@web3-storage/access": "workspace:^", "@web3-storage/capabilities": "workspace:^", + "@web3-storage/did-mailto": "workspace:^", "@web3-storage/worker-utils": "0.4.3-dev", "dotenv": "^16.0.3", "kysely": "^0.23.4", diff --git a/packages/access-api/src/routes/validate-email.js b/packages/access-api/src/routes/validate-email.js index f4bc8c234..beb666fde 100644 --- a/packages/access-api/src/routes/validate-email.js +++ b/packages/access-api/src/routes/validate-email.js @@ -4,7 +4,7 @@ import { } from '@web3-storage/access/encoding' import * as Access from '@web3-storage/capabilities/access' import QRCode from 'qrcode' -import { toEmail } from '../utils/did-mailto.js' +import * as DidMailto from '@web3-storage/did-mailto' import { HtmlResponse, ValidateEmail, @@ -173,7 +173,7 @@ async function authorize(req, env) { return new HtmlResponse( ( diff --git a/packages/access-api/src/service/access-authorize.js b/packages/access-api/src/service/access-authorize.js index 4732be9b8..270292b86 100644 --- a/packages/access-api/src/service/access-authorize.js +++ b/packages/access-api/src/service/access-authorize.js @@ -1,6 +1,6 @@ import * as Server from '@ucanto/server' import * as Access from '@web3-storage/capabilities/access' -import * as Mailto from '../utils/did-mailto.js' +import * as DidMailto from '@web3-storage/did-mailto' import { delegationToString } from '@web3-storage/access/encoding' /** @@ -52,7 +52,7 @@ export function accessAuthorizeProvider(ctx) { const url = `${ctx.url.protocol}//${ctx.url.host}/validate-email?ucan=${encoded}&mode=authorize` await ctx.email.sendValidation({ - to: Mailto.toEmail(capability.nb.iss), + to: DidMailto.toEmail(DidMailto.fromString(capability.nb.iss)), url, }) diff --git a/packages/access-api/src/utils/did-mailto.js b/packages/access-api/src/utils/did-mailto.js deleted file mode 100644 index bf2e26525..000000000 --- a/packages/access-api/src/utils/did-mailto.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * - * @param {`did:${string}:${string}`} did - * @returns - */ -export function toEmail(did) { - const parts = did.split(':') - if (parts[1] !== 'mailto') { - throw new Error(`DID ${did} is not a mailto did.`) - } - return `${decodeURIComponent(parts[3])}@${decodeURIComponent(parts[2])}` -} diff --git a/packages/access-api/test/access-authorize.test.js b/packages/access-api/test/access-authorize.test.js index 111858ded..91867c8f5 100644 --- a/packages/access-api/test/access-authorize.test.js +++ b/packages/access-api/test/access-authorize.test.js @@ -10,7 +10,7 @@ import { Accounts } from '../src/models/accounts.js' import { context } from './helpers/context.js' // @ts-ignore import isSubset from 'is-subset' -import { toEmail } from '../src/utils/did-mailto.js' +import * as DidMailto from '@web3-storage/did-mailto' import { warnOnErrorResult, registerSpaces, @@ -126,7 +126,7 @@ describe('access/authorize', function () { const html = await rsp.text() assert(html.includes('Email Validated')) - assert(html.includes(toEmail(accountDID))) + assert(html.includes(DidMailto.toEmail(accountDID))) assert(html.includes(issuer.did())) }) diff --git a/packages/access-api/test/access-claim.test.js b/packages/access-api/test/access-claim.test.js index 97bc84362..7ae227f6f 100644 --- a/packages/access-api/test/access-claim.test.js +++ b/packages/access-api/test/access-claim.test.js @@ -16,7 +16,9 @@ for (const handlerVariant of /** @type {const} */ ([ spaceWithStorageProvider, ...createTesterFromContext(() => context(), { registerSpaces: [spaceWithStorageProvider], - account: { did: () => /** @type {const} */ ('did:mailto:foo') }, + account: { + did: () => /** @type {const} */ ('did:mailto:example.com:foo'), + }, }), } })(), diff --git a/packages/access-api/test/access-client-agent.test.js b/packages/access-api/test/access-client-agent.test.js index 459475360..3b07167a3 100644 --- a/packages/access-api/test/access-client-agent.test.js +++ b/packages/access-api/test/access-client-agent.test.js @@ -4,6 +4,7 @@ import { assertNotError, createTesterFromContext, } from './helpers/ucanto-test-utils.js' +import * as DidMailto from '@web3-storage/did-mailto' import * as principal from '@ucanto/principal' import { addProvider, @@ -11,7 +12,6 @@ import { Agent as AccessAgent, authorizeAndWait, claimAccess, - createDidMailtoFromEmail, delegationsIncludeSessionProof, pollAccessClaimUntil, requestAccess, @@ -80,7 +80,7 @@ for (const accessApiVariant of /** @type {const} */ ([ const abort = new AbortController() after(() => abort.abort()) const account = { - did: () => createDidMailtoFromEmail('example@dag.house'), + did: () => DidMailto.fromEmail(DidMailto.email('example@dag.house')), } await requestAccess(accessAgent, account, [{ can: '*' }]) assert.deepEqual(emails.length, emailCount + 1) @@ -184,8 +184,8 @@ for (const accessApiVariant of /** @type {const} */ ([ it('can registerSpace', async () => { const { connection, emails } = await accessApiVariant.create() - const accountEmail = 'foo@dag.house' - const account = { did: () => createDidMailtoFromEmail(accountEmail) } + const accountEmail = DidMailto.email('foo@dag.house') + const account = { did: () => DidMailto.fromEmail(accountEmail) } const accessAgent = await AccessAgent.create(undefined, { connection, }) @@ -615,7 +615,7 @@ async function testSessionAuthorization(service, access, account, emails) { * @returns {Ucanto.DID<'mailto'>} */ function thisEmailDidMailto() { - return createDidMailtoFromEmail(this.email) + return DidMailto.fromEmail(DidMailto.email(this.email)) } /** diff --git a/packages/access-api/test/access-delegate.test.js b/packages/access-api/test/access-delegate.test.js index a9bcb2048..88a944c26 100644 --- a/packages/access-api/test/access-delegate.test.js +++ b/packages/access-api/test/access-delegate.test.js @@ -26,7 +26,9 @@ for (const handlerVariant of /** @type {const} */ ([ name: 'handled by access-api in miniflare', ...(() => { const spaceWithStorageProvider = principal.ed25519.generate() - const account = { did: () => /** @type {const} */ ('did:mailto:foo') } + const account = { + did: () => /** @type {const} */ ('did:mailto:example.com:foo'), + } return { spaceWithStorageProvider, ...createTesterFromContext(() => context(), { @@ -125,7 +127,9 @@ for (const variant of /** @type {const} */ ([ name: 'handled by access-api in miniflare', ...(() => { const spaceWithStorageProvider = principal.ed25519.generate() - const account = { did: () => /** @type {const} */ ('did:mailto:foo') } + const account = { + did: () => /** @type {const} */ ('did:mailto:example.com:foo'), + } return { spaceWithStorageProvider, ...createTesterFromContext(() => context(), { diff --git a/packages/access-api/test/provider-add.test.js b/packages/access-api/test/provider-add.test.js index 7be6fa401..46e4659b2 100644 --- a/packages/access-api/test/provider-add.test.js +++ b/packages/access-api/test/provider-add.test.js @@ -270,7 +270,7 @@ const setup = async (options = {}) => { const context = await createContextWithMailbox(options) const space = await principal.ed25519.generate() const agent = await principal.ed25519.generate() - const account = principal.Absentee.from({ id: 'did:mailto:foo' }) + const account = principal.Absentee.from({ id: 'did:mailto:example.com:foo' }) return { ...context, space, agent, account } } diff --git a/packages/access-api/test/provisions.test.js b/packages/access-api/test/provisions.test.js index 8fc8d6102..bb66553ab 100644 --- a/packages/access-api/test/provisions.test.js +++ b/packages/access-api/test/provisions.test.js @@ -65,7 +65,7 @@ describe('DbProvisions', () => { const modifiedFirstProvision = { ...firstProvision, space: /** @type {const} */ ('did:key:foo'), - account: /** @type {const} */ ('did:mailto:foo'), + account: /** @type {const} */ ('did:mailto:example.com:foo'), // note this type assertion is wrong, but useful to set up the test provider: /** @type {import('@ucanto/interface').DID<'web'>} */ ( 'did:provider:foo' diff --git a/packages/access-api/tsconfig.json b/packages/access-api/tsconfig.json index da475de58..1facffca5 100644 --- a/packages/access-api/tsconfig.json +++ b/packages/access-api/tsconfig.json @@ -8,5 +8,9 @@ }, "include": ["src", "scripts", "test", "package.json", "sql"], "exclude": ["**/node_modules/**"], - "references": [{ "path": "../access-client" }, { "path": "../capabilities" }] + "references": [ + { "path": "../access-client" }, + { "path": "../capabilities" }, + { "path": "../did-mailto" } + ] } diff --git a/packages/access-client/package.json b/packages/access-client/package.json index 387b65ce3..d073fc343 100644 --- a/packages/access-client/package.json +++ b/packages/access-client/package.json @@ -67,6 +67,7 @@ "@ucanto/transport": "^5.1.1", "@ucanto/validator": "^6.1.0", "@web3-storage/capabilities": "workspace:^", + "@web3-storage/did-mailto": "workspace:^", "bigint-mod-arith": "^3.1.2", "conf": "10.2.0", "inquirer": "^9.1.4", diff --git a/packages/access-client/src/agent-use-cases.js b/packages/access-client/src/agent-use-cases.js index 659180fcd..09306d150 100644 --- a/packages/access-client/src/agent-use-cases.js +++ b/packages/access-client/src/agent-use-cases.js @@ -1,8 +1,4 @@ -import { - addSpacesFromDelegations, - Agent as AccessAgent, - createDidMailtoFromEmail, -} from './agent.js' +import { addSpacesFromDelegations, Agent as AccessAgent } from './agent.js' import * as Ucanto from '@ucanto/interface' import * as Access from '@web3-storage/capabilities/access' import { bytesToDelegations, stringToDelegation } from './encoding.js' @@ -12,6 +8,7 @@ import { Websocket, AbortError } from './utils/ws.js' import { AgentData, isSessionProof } from './agent-data.js' import * as ucanto from '@ucanto/core' import { DID as DIDValidator } from '@ucanto/validator' +import * as DidMailto from '@web3-storage/did-mailto' /** * Request access by a session allowing this agent to issue UCANs @@ -242,7 +239,7 @@ export async function waitForAuthorizationByPolling(access, opts = {}) { export async function authorizeAndWait(access, email, opts = {}) { const expectAuthorization = opts.expectAuthorization || waitForAuthorizationByPolling - const account = { did: () => createDidMailtoFromEmail(email) } + const account = { did: () => DidMailto.fromEmail(email) } await requestAccess( access, account, @@ -337,7 +334,7 @@ export async function addProviderAndDelegateToAccount( if (spaceMeta && spaceMeta.isRegistered) { throw new Error('Space already registered with web3.storage.') } - const account = { did: () => createDidMailtoFromEmail(email) } + const account = { did: () => DidMailto.fromEmail(DidMailto.email(email)) } await addProvider({ access, space, account, provider }) const delegateSpaceAccessResult = await delegateSpaceAccessToAccount( access, diff --git a/packages/access-client/src/agent.js b/packages/access-client/src/agent.js index 1532a4461..516653236 100644 --- a/packages/access-client/src/agent.js +++ b/packages/access-client/src/agent.js @@ -24,13 +24,12 @@ import { canDelegateCapability, } from './delegations.js' import { AgentData, getSessionProofs } from './agent-data.js' -import { createDidMailtoFromEmail } from './utils/did-mailto.js' import { addProviderAndDelegateToAccount, waitForDelegationOnSocket, } from './agent-use-cases.js' -export { AgentData, createDidMailtoFromEmail } +export { AgentData } export * from './agent-use-cases.js' const HOST = 'https://access.web3.storage' diff --git a/packages/access-client/src/utils/did-mailto.js b/packages/access-client/src/utils/did-mailto.js deleted file mode 100644 index 45480755e..000000000 --- a/packages/access-client/src/utils/did-mailto.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @param {string} email - * @returns {`did:mailto:${string}:${string}`} - */ -export function createDidMailtoFromEmail(email) { - const emailParts = email.split('@') - if (emailParts.length !== 2) { - throw new Error(`unexpected email ${email}`) - } - const [local, domain] = emailParts - const did = /** @type {const} */ ( - `did:mailto:${encodeURIComponent(domain)}:${encodeURIComponent(local)}` - ) - return did -} diff --git a/packages/access-client/test/agent.test.js b/packages/access-client/test/agent.test.js index 319e954a0..8c8f401fa 100644 --- a/packages/access-client/test/agent.test.js +++ b/packages/access-client/test/agent.test.js @@ -1,6 +1,6 @@ import assert from 'assert' import { URI } from '@ucanto/validator' -import { Agent, connection, createDidMailtoFromEmail } from '../src/agent.js' +import { Agent, connection } from '../src/agent.js' import * as Space from '@web3-storage/capabilities/space' import { createServer } from './helpers/utils.js' import * as fixtures from './helpers/fixtures.js' @@ -253,11 +253,4 @@ describe('Agent', function () { /cannot delegate capability store\/remove/ ) }) - - it('exports createDidMailtoFromEmail', async () => { - assert.deepEqual( - createDidMailtoFromEmail('foo@dag.house'), - 'did:mailto:dag.house:foo' - ) - }) }) diff --git a/packages/access-client/tsconfig.json b/packages/access-client/tsconfig.json index 8f9db5b03..e3b39cb5c 100644 --- a/packages/access-client/tsconfig.json +++ b/packages/access-client/tsconfig.json @@ -7,5 +7,5 @@ }, "include": ["src", "scripts", "test", "package.json"], "exclude": ["**/node_modules/**"], - "references": [{ "path": "../capabilities" }] + "references": [{ "path": "../capabilities" }, { "path": "../did-mailto" }] } diff --git a/packages/did-mailto/package.json b/packages/did-mailto/package.json new file mode 100644 index 000000000..946222408 --- /dev/null +++ b/packages/did-mailto/package.json @@ -0,0 +1,67 @@ +{ + "name": "@web3-storage/did-mailto", + "version": "1.0.0", + "description": "did:mailto", + "repository": { + "type": "git", + "url": "https://github.com/web3-storage/w3up.git", + "directory": "packages/did-mailto" + }, + "license": "(Apache-2.0 OR MIT)", + "type": "module", + "types": "dist/src/index.d.ts", + "main": "src/index.js", + "files": [ + "src", + "test", + "dist/**/*.d.ts", + "dist/**/*.d.ts.map" + ], + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./src/index.js" + } + }, + "scripts": { + "build": "tsc --build", + "check": "tsc --build", + "lint": "tsc --build", + "test": "mocha --bail --timeout 10s -n no-warnings -n experimental-vm-modules -n experimental-fetch test/**/*.spec.js", + "test-watch": "pnpm build && mocha --bail --timeout 10s --watch --parallel -n no-warnings -n experimental-vm-modules -n experimental-fetch --watch-files src,test" + }, + "devDependencies": { + "@types/mocha": "^10.0.1", + "@types/node": "^18.11.18", + "hd-scripts": "^4.1.0", + "mocha": "^10.2.0" + }, + "eslintConfig": { + "extends": [ + "./node_modules/hd-scripts/eslint/index.js" + ], + "parserOptions": { + "project": "./tsconfig.json" + }, + "rules": { + "jsdoc/no-undefined-types": [ + "error", + { + "definedTypes": [ + "Iterable" + ] + } + ] + }, + "env": { + "mocha": true + }, + "ignorePatterns": [ + "dist", + "coverage" + ] + }, + "engines": { + "node": ">=16.15" + } +} diff --git a/packages/did-mailto/src/index.js b/packages/did-mailto/src/index.js new file mode 100644 index 000000000..c41ccd835 --- /dev/null +++ b/packages/did-mailto/src/index.js @@ -0,0 +1,74 @@ +export const foo = 1 + +/** + * create a did:mailto from an email address + * + * @param {import("./types").EmailAddress} email + * @returns {import("./types").DidMailto} + */ +export function fromEmail(email) { + const { domain, local } = parseEmail(email) + const did = /** @type {const} */ ( + `did:mailto:${encodeURIComponent(domain)}:${encodeURIComponent(local)}` + ) + return did +} + +/** + * @param {import("./types").DidMailto} did + * @returns {import("./types").EmailAddress} + */ +export function toEmail(did) { + const parts = did.split(':') + if (parts[1] !== 'mailto') { + throw new Error(`DID ${did} is not a mailto did.`) + } + return `${decodeURIComponent(parts[3])}@${decodeURIComponent(parts[2])}` +} + +/** + * given a string, if it is an EmailAddress, return it, otherwise throw an error. + * Use this to parse string input to `EmailAddress` type to pass to `fromEmail` (when needed). + * This is not meant to be a general RFC5322 (et al) email address validator, which would be more expensive. + * + * @param {string} input + * @returns {import("./types").EmailAddress} + */ +export function email(input) { + const { domain, local } = parseEmail(input) + /** @type {import("./types").EmailAddress} */ + const emailAddress = `${local}@${domain}` + return emailAddress +} + +/** + * parse a did mailto from a string + * + * @param {string} input + * @returns {import("./types").DidMailto} + */ +export function fromString(input) { + const colonParts = input.split(':') + if (colonParts.length !== 4) { + throw new TypeError( + `expected did:mailto to have 4 colon-delimited segments, but got ${colonParts.length}` + ) + } + const [domain, local] = [colonParts[2], colonParts[3]] + return `did:mailto:${domain}:${local}` +} + +/** + * @param {string} email + */ +function parseEmail(email) { + const atParts = email.split('@') + if (atParts.length < 2) { + throw new TypeError( + `expected at least 2 @-delimtied segments, but got ${atParts.length}` + ) + } + const domain = atParts.at(-1) ?? '' + const local = atParts.slice(0, -1).join('@') + return { domain, local } +} diff --git a/packages/did-mailto/src/types.ts b/packages/did-mailto/src/types.ts new file mode 100644 index 000000000..51c2a45b5 --- /dev/null +++ b/packages/did-mailto/src/types.ts @@ -0,0 +1,12 @@ +// https://datatracker.ietf.org/doc/html/rfc5322#section-3.4.1 +export type LocalPart = string +export type Domain = string +export type EmailAddress = `${LocalPart}@${Domain}` + +// https://www.rfc-editor.org/rfc/rfc3986#section-2.1 +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export type PercentEncoded = string + +// did:mailto +export type DidMailto = + `did:mailto:${PercentEncoded}:${PercentEncoded}` diff --git a/packages/did-mailto/test/did-mailto.spec.js b/packages/did-mailto/test/did-mailto.spec.js new file mode 100644 index 000000000..216054266 --- /dev/null +++ b/packages/did-mailto/test/did-mailto.spec.js @@ -0,0 +1,88 @@ +import * as assert from 'assert' +import * as didMailto from '../src/index.js' + +describe('did-mailto', () => { + testDidMailto(didMailto, async (name, test) => it(name, test)) +}) + +/** + * @param {typeof didMailto} didMailto + * @param {import("./test-types").TestAdder} test + */ +function testDidMailto(didMailto, test) { + test('module is an object', async () => { + assert.equal(typeof didMailto, 'object') + }) + for (const { email, did } of examples()) { + test(`fromEmail("${email}")`, async () => { + assert.deepStrictEqual( + didMailto.fromEmail(email), + did + ) + }) + test(`toEmail("${did}")`, async () => { + assert.deepStrictEqual( + didMailto.toEmail(did), + email + ) + }) + test(`toEmail(fromEmail("${email}"))`, async () => { + assert.deepStrictEqual( + didMailto.toEmail(didMailto.fromEmail(email)), + email + ) + }) + test(`fromEmail(toEmail("${did}"))`, async () => { + assert.deepStrictEqual( + didMailto.fromEmail(didMailto.toEmail(did)), + did + ) + }) + } + for (const email of validEmailAddresses()) { + test(`email("${email}")`, async () => { + assert.doesNotThrow( + () => didMailto.email(email), + 'can parse to email' + ) + }) + } +} + +function* examples() { + yield { + email: didMailto.email('example+123@example.com'), + did: didMailto.fromString('did:mailto:example.com:example%2B123'), + } + yield { + email: didMailto.email('"email@1"@example.com'), + did: didMailto.fromString(`did:mailto:example.com:%22email%401%22`), + } +} + +function* validEmailAddresses() { + // https://gist.github.com/cjaoude/fd9910626629b53c4d25#file-gistfile1-txt-L5 + yield* [ + 'email@example.com', + 'firstname.lastname@example.com', + 'email@subdomain.example.com', + 'firstname+lastname@example.com', + 'email@123.123.123.123', + 'email@[123.123.123.123]', + '"email"@example.com', + '"email@1"@example.com', + '1234567890@example.com', + 'email@example-one.com', + '_______@example.com', + 'email@example.name', + 'email@example.museum', + 'email@example.co.jp', + 'firstname-lastname@example.com', + ] + // https://gist.github.com/cjaoude/fd9910626629b53c4d25#file-gistfile1-txt-L24 + yield* [ + 'much.”more\\ unusual”@example.com', + 'very.unusual.”@”.unusual.com@example.com', + 'very.”(),:;<>[]”.VERY.”very@\\ "very”.unusual@strange.example.com', + ] +} diff --git a/packages/did-mailto/test/test-types.ts b/packages/did-mailto/test/test-types.ts new file mode 100644 index 000000000..9f53b588d --- /dev/null +++ b/packages/did-mailto/test/test-types.ts @@ -0,0 +1,5 @@ +// similar to mocha `it` +export type TestAdder = ( + name: string, + runTest: () => Promise +) => Promise diff --git a/packages/did-mailto/tsconfig.json b/packages/did-mailto/tsconfig.json new file mode 100644 index 000000000..ab5d5d625 --- /dev/null +++ b/packages/did-mailto/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src", "test"], + "exclude": ["**/node_modules/**", "dist"], + "references": [] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f81731ab..022f6be64 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,7 @@ importers: '@ucanto/validator': ^6.1.0 '@web3-storage/access': workspace:^ '@web3-storage/capabilities': workspace:^ + '@web3-storage/did-mailto': workspace:^ '@web3-storage/worker-utils': 0.4.3-dev better-sqlite3: 8.0.1 buffer: ^6.0.3 @@ -83,6 +84,7 @@ importers: '@ucanto/validator': 6.1.0 '@web3-storage/access': link:../access-client '@web3-storage/capabilities': link:../capabilities + '@web3-storage/did-mailto': link:../did-mailto '@web3-storage/worker-utils': 0.4.3-dev dotenv: 16.0.3 kysely: 0.23.4 @@ -141,6 +143,7 @@ importers: '@ucanto/transport': ^5.1.1 '@ucanto/validator': ^6.1.0 '@web3-storage/capabilities': workspace:^ + '@web3-storage/did-mailto': workspace:^ assert: ^2.0.0 bigint-mod-arith: ^3.1.2 conf: 10.2.0 @@ -173,6 +176,7 @@ importers: '@ucanto/transport': 5.1.1 '@ucanto/validator': 6.1.0 '@web3-storage/capabilities': link:../capabilities + '@web3-storage/did-mailto': link:../did-mailto bigint-mod-arith: 3.1.2 conf: 10.2.0 inquirer: 9.1.4 @@ -240,6 +244,18 @@ importers: typescript: 4.9.5 watch: 1.0.2 + packages/did-mailto: + specifiers: + '@types/mocha': ^10.0.1 + '@types/node': ^18.11.18 + hd-scripts: ^4.1.0 + mocha: ^10.2.0 + devDependencies: + '@types/mocha': 10.0.1 + '@types/node': 18.15.5 + hd-scripts: 4.1.0 + mocha: 10.2.0 + packages/upload-api: specifiers: '@ipld/car': ^5.1.1