@cef-ebsi/ebsi-uri 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,97 @@
1
+ export const SCHEME = "ebsi";
2
+ export const PRODUCTION_NETWORK = "production";
3
+ export const NETWORKS = [
4
+ PRODUCTION_NETWORK,
5
+ "preprod",
6
+ "conformance",
7
+ "pilot",
8
+ "test",
9
+ ];
10
+ export const HOSTS = {
11
+ test: "api-test.ebsi.eu",
12
+ pilot: "api-pilot.ebsi.",
13
+ conformance: "api-conformance.ebsi.eu",
14
+ preprod: "api-preprod.ebsi.",
15
+ production: "api.ebsi.",
16
+ };
17
+ function isNetwork(network) {
18
+ return network !== undefined && NETWORKS.includes(network);
19
+ }
20
+ export function parse(uriString) {
21
+ const [scheme, firstComponent, secondComponent, ...rest] = uriString.split(":");
22
+ if (scheme !== SCHEME) {
23
+ throw new Error("Invalid URI: scheme must be 'ebsi'");
24
+ }
25
+ const network = isNetwork(firstComponent)
26
+ ? firstComponent
27
+ : PRODUCTION_NETWORK;
28
+ const service = isNetwork(firstComponent) ? secondComponent : firstComponent;
29
+ if (!service || service.startsWith("/")) {
30
+ throw new Error(`No service found in EBSI URI '${uriString}'`);
31
+ }
32
+ const resource = (isNetwork(firstComponent) ? rest : [secondComponent, ...rest]).join(":");
33
+ if (!resource) {
34
+ throw new Error(`No resource found in EBSI URI '${uriString}'`);
35
+ }
36
+ return {
37
+ network,
38
+ service,
39
+ resource,
40
+ };
41
+ }
42
+ export function serialize(components) {
43
+ const { network, service, resource } = components;
44
+ const networkName = network === PRODUCTION_NETWORK ? "" : `${network}:`;
45
+ return `${SCHEME}:${networkName}${service}:${resource}`;
46
+ }
47
+ export function toUrl(uriString, options) {
48
+ const { network, service, resource } = parse(uriString);
49
+ const host = options.hosts[network];
50
+ if (!host) {
51
+ throw new Error(`No host for network ${network}`);
52
+ }
53
+ const serviceVersion = options.services[service];
54
+ if (serviceVersion === undefined) {
55
+ throw new Error(`Unsupported service: ${service}`);
56
+ }
57
+ return `https://${host}/${service}/${serviceVersion}${resource}`;
58
+ }
59
+ export function fromUrl(url, options) {
60
+ let urlObject;
61
+ try {
62
+ urlObject = new URL(url);
63
+ }
64
+ catch {
65
+ throw new Error("Invalid URL");
66
+ }
67
+ const { host, pathname, search, hash } = urlObject;
68
+ const network = Object.keys(HOSTS).find((n) => host.includes(HOSTS[n]));
69
+ if (!network) {
70
+ throw new Error(`No network configured for host ${host}`);
71
+ }
72
+ const [service, serviceVersion, ...rest] = pathname
73
+ .split("/")
74
+ .filter(Boolean);
75
+ const resource = rest.join("/");
76
+ if (!service) {
77
+ throw new Error(`No service found in EBSI URI ${url}`);
78
+ }
79
+ if (!serviceVersion) {
80
+ throw new Error(`No service version found in EBSI URI ${url}`);
81
+ }
82
+ if (options?.services) {
83
+ const supportedServiceVersion = options.services[service];
84
+ if (!supportedServiceVersion) {
85
+ throw new Error(`Unsupported service '${service}'`);
86
+ }
87
+ if (serviceVersion !== supportedServiceVersion) {
88
+ throw new Error(`Unsupported version '${serviceVersion}' of service '${service}'`);
89
+ }
90
+ }
91
+ return serialize({
92
+ network,
93
+ service,
94
+ resource: `/${resource}${search}${hash}`,
95
+ });
96
+ }
97
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { DIDResolver } from "did-resolver";
2
+ export interface ResolverOptions {
3
+ registry?: string;
4
+ }
5
+ export declare function getResolver(resolverOptions: ResolverOptions): Record<"ebsi", DIDResolver>;
6
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,WAAW,EAEZ,MAAM,cAAc,CAAC;AAKtB,MAAM,WAAW,eAAe;IAI9B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD,wBAAgB,WAAW,CACzB,eAAe,EAAE,eAAe,GAC/B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAqC7B"}
@@ -0,0 +1,34 @@
1
+ import { DID_RESOLUTION_ERROR_CODES } from "./constants.js";
2
+ import { resolve as resolveV1 } from "./resolvers/v1.js";
3
+ import { validate } from "./validator.js";
4
+ export function getResolver(resolverOptions) {
5
+ async function resolve(did, _, __, didResolutionOptions) {
6
+ if (!resolverOptions || typeof resolverOptions !== "object") {
7
+ return {
8
+ didDocument: null,
9
+ didDocumentMetadata: {},
10
+ didResolutionMetadata: {
11
+ error: DID_RESOLUTION_ERROR_CODES.CONFIGURATION_ERROR,
12
+ message: "Invalid or missing resolver options",
13
+ },
14
+ };
15
+ }
16
+ try {
17
+ validate(did);
18
+ }
19
+ catch (e) {
20
+ const message = e instanceof Error ? e.message : "Unknown error";
21
+ return {
22
+ didDocument: null,
23
+ didDocumentMetadata: {},
24
+ didResolutionMetadata: {
25
+ error: "invalidDid",
26
+ message,
27
+ },
28
+ };
29
+ }
30
+ return resolveV1(did, resolverOptions.registry, didResolutionOptions);
31
+ }
32
+ return { ebsi: resolve };
33
+ }
34
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAY1C,MAAM,UAAU,WAAW,CACzB,eAAgC;IAEhC,KAAK,UAAU,OAAO,CACpB,GAAW,EACX,CAAY,EACZ,EAAW,EACX,oBAA2C;QAE3C,IAAI,CAAC,eAAe,IAAI,OAAO,eAAe,KAAK,QAAQ,EAAE,CAAC;YAC5D,OAAO;gBACL,WAAW,EAAE,IAAI;gBACjB,mBAAmB,EAAE,EAAE;gBACvB,qBAAqB,EAAE;oBACrB,KAAK,EAAE,0BAA0B,CAAC,mBAAmB;oBACrD,OAAO,EAAE,qCAAqC;iBAC/C;aACF,CAAC;QACJ,CAAC;QAGD,IAAI,CAAC;YACH,QAAQ,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACjE,OAAO;gBACL,WAAW,EAAE,IAAI;gBACjB,mBAAmB,EAAE,EAAE;gBACvB,qBAAqB,EAAE;oBACrB,KAAK,EAAE,YAAY;oBACnB,OAAO;iBACR;aACF,CAAC;QACJ,CAAC;QAED,OAAO,SAAS,CAAC,GAAG,EAAE,eAAe,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { DIDResolutionOptions, DIDResolutionResult } from "did-resolver";
2
+ export declare function resolve(did: string, registry?: string, didResolutionOptions?: DIDResolutionOptions): Promise<DIDResolutionResult>;
3
+ //# sourceMappingURL=v1.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1.d.ts","sourceRoot":"","sources":["../../src/resolvers/v1.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAyDtB,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,oBAAoB,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CA+E9B"}
@@ -0,0 +1,109 @@
1
+ import axios from "axios";
2
+ import { ProblemDetailsError } from "../errors.js";
3
+ import { DID_RESOLUTION_ERROR_CODES, } from "../constants.js";
4
+ const AXIOS_TIMEOUT = 15_000;
5
+ const SUPPORTED_CONTENT_TYPES = [
6
+ "application/did+ld+json",
7
+ "application/did+json",
8
+ ];
9
+ function isSupportedContentType(contentType) {
10
+ return SUPPORTED_CONTENT_TYPES.includes(contentType);
11
+ }
12
+ async function get(url, contentType, timeout) {
13
+ try {
14
+ const response = await axios.get(url, {
15
+ headers: {
16
+ accept: contentType,
17
+ },
18
+ timeout: timeout || AXIOS_TIMEOUT,
19
+ });
20
+ return response.data;
21
+ }
22
+ catch (e) {
23
+ let message = "";
24
+ if (axios.isAxiosError(e)) {
25
+ try {
26
+ const { detail, title } = e.response?.data;
27
+ message = detail || title;
28
+ }
29
+ catch (err) {
30
+ message = e.message;
31
+ }
32
+ throw new ProblemDetailsError(e.response?.status || 500, e.response?.statusText || "", {
33
+ detail: message,
34
+ });
35
+ }
36
+ throw e;
37
+ }
38
+ }
39
+ export async function resolve(did, registry, didResolutionOptions) {
40
+ if (!registry) {
41
+ return {
42
+ didDocument: null,
43
+ didDocumentMetadata: {},
44
+ didResolutionMetadata: {
45
+ error: DID_RESOLUTION_ERROR_CODES.CONFIGURATION_ERROR,
46
+ message: "A DID registry must be defined in order to resolve EBSI DID method v1 documents",
47
+ },
48
+ };
49
+ }
50
+ let accept = "application/did+ld+json";
51
+ if (didResolutionOptions && didResolutionOptions.accept) {
52
+ if (!isSupportedContentType(didResolutionOptions.accept)) {
53
+ return {
54
+ didDocument: null,
55
+ didDocumentMetadata: {},
56
+ didResolutionMetadata: {
57
+ error: DID_RESOLUTION_ERROR_CODES.REPRESENTATION_NOT_SUPPORTED,
58
+ message: `Representation ${didResolutionOptions.accept} is not supported. Valid representations include: ${SUPPORTED_CONTENT_TYPES.join(", ")}`,
59
+ },
60
+ };
61
+ }
62
+ accept = didResolutionOptions.accept;
63
+ }
64
+ try {
65
+ const timeout = didResolutionOptions &&
66
+ typeof didResolutionOptions["timeout"] === "number"
67
+ ? didResolutionOptions["timeout"]
68
+ : AXIOS_TIMEOUT;
69
+ const didDocument = await get(`${registry}/${did}`, accept, timeout);
70
+ return {
71
+ didDocument,
72
+ didDocumentMetadata: {},
73
+ didResolutionMetadata: { contentType: accept },
74
+ };
75
+ }
76
+ catch (e) {
77
+ let error = DID_RESOLUTION_ERROR_CODES.UNKNOWN_ERROR;
78
+ let message = "";
79
+ if (e instanceof ProblemDetailsError) {
80
+ if (e.status === 400) {
81
+ error = DID_RESOLUTION_ERROR_CODES.INVALID_DID;
82
+ message = e.detail || e.title || "Bad Request";
83
+ }
84
+ else if (e.status === 404) {
85
+ error = DID_RESOLUTION_ERROR_CODES.NOT_FOUND;
86
+ message = e.detail || e.title || "Not Found";
87
+ }
88
+ else if (e.status === 500) {
89
+ error = DID_RESOLUTION_ERROR_CODES.INTERNAL_SERVER_ERROR;
90
+ message = e.detail || e.title || "Internal Server Error";
91
+ }
92
+ else {
93
+ message = e.detail || e.title || "unknown";
94
+ }
95
+ }
96
+ else if (e instanceof Error) {
97
+ message = e.message;
98
+ }
99
+ return {
100
+ didDocument: null,
101
+ didDocumentMetadata: {},
102
+ didResolutionMetadata: {
103
+ error,
104
+ message: `${message} | Registry used: ${registry}`,
105
+ },
106
+ };
107
+ }
108
+ }
109
+ //# sourceMappingURL=v1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1.js","sourceRoot":"","sources":["../../src/resolvers/v1.ts"],"names":[],"mappings":"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"}
package/dist/util.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { SUPPORTED_VERSIONS } from "./constants.js";
2
+ export declare function createDid(subjectIdentifierBytes: ArrayLike<number>, version?: (typeof SUPPORTED_VERSIONS)[number]): string;
3
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,kBAAkB,EACnB,MAAM,gBAAgB,CAAC;AASxB,wBAAgB,SAAS,CACvB,sBAAsB,EAAE,SAAS,CAAC,MAAM,CAAC,EACzC,OAAO,GAAE,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAgB,GAC1D,MAAM,CAmBR"}
package/dist/util.js ADDED
@@ -0,0 +1,13 @@
1
+ import { base58btc } from "multiformats/bases/base58";
2
+ import { EBSI_DID_METHOD_PREFIX, EBSI_DID_SPECS, LEGAL_ENTITY, } from "./constants.js";
3
+ export function createDid(subjectIdentifierBytes, version = LEGAL_ENTITY) {
4
+ const bytesArray = new Uint8Array(1 + EBSI_DID_SPECS[version].BYTE_LENGTH);
5
+ bytesArray.set([EBSI_DID_SPECS[version].VERSION_ID]);
6
+ if (subjectIdentifierBytes.length !== EBSI_DID_SPECS[version].BYTE_LENGTH) {
7
+ throw new Error(`Invalid subject identifier length. Expected: ${EBSI_DID_SPECS[version].BYTE_LENGTH} bytes. Received: ${subjectIdentifierBytes.length} bytes.`);
8
+ }
9
+ bytesArray.set(subjectIdentifierBytes, 1);
10
+ const methodSpecificIdentifier = base58btc.encode(bytesArray);
11
+ return `${EBSI_DID_METHOD_PREFIX}${methodSpecificIdentifier}`;
12
+ }
13
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,YAAY,GAEb,MAAM,gBAAgB,CAAC;AASxB,MAAM,UAAU,SAAS,CACvB,sBAAyC,EACzC,UAA+C,YAAY;IAG3D,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC;IAG3E,UAAU,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IAErD,IAAI,sBAAsB,CAAC,MAAM,KAAK,cAAc,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1E,MAAM,IAAI,KAAK,CACb,gDAAgD,cAAc,CAAC,OAAO,CAAC,CAAC,WAAW,qBAAqB,sBAAsB,CAAC,MAAM,SAAS,CAC/I,CAAC;IACJ,CAAC;IAED,UAAU,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC;IAG1C,MAAM,wBAAwB,GAAG,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAE9D,OAAO,GAAG,sBAAsB,GAAG,wBAAwB,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function validate(did: string): number;
2
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAaA,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAsD5C"}
@@ -0,0 +1,35 @@
1
+ import { base58btc } from "multiformats/bases/base58";
2
+ import { EBSI_DID_METHOD_PREFIX, EBSI_DID_SPECS, SUPPORTED_VERSIONS, } from "./constants.js";
3
+ export function validate(did) {
4
+ if (!did || typeof did !== "string") {
5
+ throw new Error("The DID must be a string");
6
+ }
7
+ if (!did.startsWith(EBSI_DID_METHOD_PREFIX)) {
8
+ throw new Error(`The DID must start with "${EBSI_DID_METHOD_PREFIX}"`);
9
+ }
10
+ const methodSpecificIdentifier = did.substring(EBSI_DID_METHOD_PREFIX.length);
11
+ if (!methodSpecificIdentifier.startsWith("z")) {
12
+ throw new Error('The method-specific identifier must start with "z" (multibase base58btc-encoded)');
13
+ }
14
+ let decodedIdentifier;
15
+ try {
16
+ decodedIdentifier = base58btc.decode(methodSpecificIdentifier);
17
+ }
18
+ catch (e) {
19
+ throw new Error("The method-specific identifier is not a valid multibase base58btc-encoded string");
20
+ }
21
+ const version = decodedIdentifier[0];
22
+ if (!version) {
23
+ throw new Error("No version found");
24
+ }
25
+ const didSpecsKey = SUPPORTED_VERSIONS.find((specKey) => version === EBSI_DID_SPECS[specKey].VERSION_ID);
26
+ if (!didSpecsKey) {
27
+ throw new Error(`Unsupported version "${version}"`);
28
+ }
29
+ if (decodedIdentifier.byteLength - 1 !==
30
+ EBSI_DID_SPECS[didSpecsKey].BYTE_LENGTH) {
31
+ throw new Error(`The subject identifier length (${decodedIdentifier.byteLength - 1} bytes) does not match the expected length (${EBSI_DID_SPECS[didSpecsKey].BYTE_LENGTH} bytes)`);
32
+ }
33
+ return version;
34
+ }
35
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAQxB,MAAM,UAAU,QAAQ,CAAC,GAAW;IAClC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,sBAAsB,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,4BAA4B,sBAAsB,GAAG,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,wBAAwB,GAAG,GAAG,CAAC,SAAS,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAE9E,IAAI,CAAC,wBAAwB,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAC;IACJ,CAAC;IAED,IAAI,iBAA6B,CAAC;IAClC,IAAI,CAAC;QACH,iBAAiB,GAAG,SAAS,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAErC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,WAAW,GAAG,kBAAkB,CAAC,IAAI,CACzC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,cAAc,CAAC,OAAO,CAAC,CAAC,UAAU,CAC5D,CAAC;IAEF,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,wBAAwB,OAAO,GAAG,CAAC,CAAC;IACtD,CAAC;IAED,IACE,iBAAiB,CAAC,UAAU,GAAG,CAAC;QAChC,cAAc,CAAC,WAAW,CAAC,CAAC,WAAW,EACvC,CAAC;QACD,MAAM,IAAI,KAAK,CACb,kCACE,iBAAiB,CAAC,UAAU,GAAG,CACjC,+CACE,cAAc,CAAC,WAAW,CAAC,CAAC,WAC9B,SAAS,CACV,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@cef-ebsi/ebsi-uri",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "EBSI URIs parser and serializer",
5
+ "license": "EUPL-1.2",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "repository": {
10
+ "type": "bitbucket",
11
+ "url": "https://ec.europa.eu/digital-building-blocks/code/scm/ebsi/core-libs.git",
12
+ "directory": "packages/ebsi-uri"
13
+ },
14
+ "type": "module",
15
+ "exports": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "sideEffects": false,
18
+ "files": [
19
+ "dist",
20
+ "src"
21
+ ],
22
+ "scripts": {
23
+ "prepare": "yarn build",
24
+ "build": "tsc -p tsconfig/tsconfig.build.esm.json",
25
+ "clean": "rimraf dist coverage",
26
+ "lint": "yarn lint:tsc",
27
+ "lint:tsc": "tsc --noEmit --incremental false",
28
+ "test": "vitest --run",
29
+ "test:unit": "yarn test unit",
30
+ "test:e2e": "echo \"Info: there's no e2e tests for @cef-ebsi/ebsi-uri\" && exit 0",
31
+ "test:ci": "yarn test:unit --coverage --reporter=default --reporter=vitest-sonar-reporter"
32
+ },
33
+ "gitHead": "b504c213dd26aeadb1e29da81c731dddea581006"
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1,230 @@
1
+ export const SCHEME = "ebsi";
2
+
3
+ export const PRODUCTION_NETWORK = "production";
4
+
5
+ export const NETWORKS = [
6
+ PRODUCTION_NETWORK,
7
+ "preprod",
8
+ "conformance",
9
+ "pilot",
10
+ "test",
11
+ ] as const;
12
+
13
+ export const HOSTS = {
14
+ test: "api-test.ebsi.eu",
15
+ pilot: "api-pilot.ebsi.",
16
+ conformance: "api-conformance.ebsi.eu",
17
+ preprod: "api-preprod.ebsi.",
18
+ production: "api.ebsi.",
19
+ } as const satisfies Record<Network, string>;
20
+
21
+ export type Network = (typeof NETWORKS)[number];
22
+
23
+ export type EBSIURIComponents = {
24
+ network?: Network;
25
+ service?: string;
26
+ resource?: string;
27
+ };
28
+
29
+ export type SupportedServices = Record<string, string>;
30
+
31
+ function isNetwork(network: string | undefined): network is Network {
32
+ return network !== undefined && NETWORKS.includes(network as Network);
33
+ }
34
+
35
+ /**
36
+ * Parse an EBSI URI.
37
+ * @example
38
+ * ```js
39
+ * parse("ebsi:test:did-registry:/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw")
40
+ * ```
41
+ * returns
42
+ * ```json
43
+ * {
44
+ * "network": "test",
45
+ * "service": "did-registry",
46
+ * "resource": "/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw"
47
+ * }
48
+ * ```
49
+ * @param uriString - EBSI URI to parse, e.g. "ebsi:test:did-registry:/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw"
50
+ * @returns EBSI URI components
51
+ */
52
+ export function parse(uriString: string) {
53
+ const [scheme, firstComponent, secondComponent, ...rest] =
54
+ uriString.split(":");
55
+
56
+ if (scheme !== SCHEME) {
57
+ throw new Error("Invalid URI: scheme must be 'ebsi'");
58
+ }
59
+
60
+ const network: Network = isNetwork(firstComponent)
61
+ ? firstComponent
62
+ : PRODUCTION_NETWORK;
63
+
64
+ const service = isNetwork(firstComponent) ? secondComponent : firstComponent;
65
+
66
+ if (!service || service.startsWith("/")) {
67
+ throw new Error(`No service found in EBSI URI '${uriString}'`);
68
+ }
69
+
70
+ const resource = (
71
+ isNetwork(firstComponent) ? rest : [secondComponent, ...rest]
72
+ ).join(":");
73
+
74
+ if (!resource) {
75
+ throw new Error(`No resource found in EBSI URI '${uriString}'`);
76
+ }
77
+
78
+ // Return EBSI URI components
79
+ return {
80
+ network,
81
+ service,
82
+ resource,
83
+ } satisfies EBSIURIComponents;
84
+ }
85
+
86
+ /**
87
+ * Serialize EBSI URI components.
88
+ * @example
89
+ * ```js
90
+ * serialize({
91
+ * "network": "test",
92
+ * "service": "did-registry",
93
+ * "resource": "/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw"
94
+ * })
95
+ * ```
96
+ * returns `ebsi:test:did-registry:/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw`
97
+ * @param components - EBSI URI components
98
+ * @returns EBSI URI
99
+ */
100
+ export function serialize(components: EBSIURIComponents) {
101
+ const { network, service, resource } = components;
102
+
103
+ const networkName = network === PRODUCTION_NETWORK ? "" : `${network}:`;
104
+
105
+ // Return EBSI URI
106
+ return `${SCHEME}:${networkName}${service}:${resource}`;
107
+ }
108
+
109
+ /**
110
+ * Transform an EBSI URI into a URL.
111
+ * @example
112
+ * ```js
113
+ * toUrl("ebsi:test:did-registry:/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw", {
114
+ * services: { "did-registry": "v4" },
115
+ * hosts: { test: "api-test.ebsi.eu" }
116
+ * })
117
+ * ```
118
+ * returns `https://api-test.ebsi.eu/did-registry/v4/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw`
119
+ * @param uriString - EBSI URI
120
+ * @param options - Supported services and hosts
121
+ * @returns URL
122
+ */
123
+ export function toUrl(
124
+ uriString: string,
125
+ options: {
126
+ /**
127
+ * Map of supported services and their versions
128
+ */
129
+ readonly services: SupportedServices;
130
+
131
+ /**
132
+ * Map of supported networks and their hosts
133
+ */
134
+ readonly hosts: Partial<Record<Network, string>>;
135
+ },
136
+ ): string {
137
+ const { network, service, resource } = parse(uriString);
138
+
139
+ // Replace host
140
+ const host = options.hosts[network];
141
+
142
+ if (!host) {
143
+ throw new Error(`No host for network ${network}`);
144
+ }
145
+
146
+ // Get service version
147
+ const serviceVersion = options.services[service];
148
+
149
+ if (serviceVersion === undefined) {
150
+ throw new Error(`Unsupported service: ${service}`);
151
+ }
152
+
153
+ // Return URL
154
+ return `https://${host}/${service}/${serviceVersion}${resource}`;
155
+ }
156
+
157
+ /**
158
+ * Transform a URL into an EBSI URI.
159
+ * @example
160
+ * ```js
161
+ * fromUrl("https://api-test.ebsi.eu/did-registry/v4/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw")
162
+ * ```
163
+ * returns `ebsi:test:did-registry:/identifiers/did:ebsi:zx23VhpbGYeqF2MRJk3HzPw`
164
+ * @param url - URL to transform
165
+ * @param options - Supported services
166
+ * @returns EBSI URI
167
+ */
168
+ export function fromUrl(
169
+ url: string,
170
+ options?: {
171
+ /**
172
+ * Map of supported services and their versions
173
+ */
174
+ services?: SupportedServices;
175
+ },
176
+ ) {
177
+ // Parse URL
178
+ let urlObject: URL;
179
+ try {
180
+ urlObject = new URL(url);
181
+ } catch {
182
+ throw new Error("Invalid URL");
183
+ }
184
+
185
+ const { host, pathname, search, hash } = urlObject;
186
+
187
+ // Identify network
188
+ const network = (Object.keys(HOSTS) as (keyof typeof HOSTS)[]).find((n) =>
189
+ host.includes(HOSTS[n]),
190
+ );
191
+
192
+ if (!network) {
193
+ throw new Error(`No network configured for host ${host}`);
194
+ }
195
+
196
+ // Extract service and resource from pathname: /<service>/<serviceVersion>/<resource>
197
+ const [service, serviceVersion, ...rest] = pathname
198
+ .split("/")
199
+ .filter(Boolean); // Remove empty path segments
200
+ const resource = rest.join("/");
201
+
202
+ if (!service) {
203
+ throw new Error(`No service found in EBSI URI ${url}`);
204
+ }
205
+
206
+ if (!serviceVersion) {
207
+ throw new Error(`No service version found in EBSI URI ${url}`);
208
+ }
209
+
210
+ // Check if the service and its version are supported
211
+ if (options?.services) {
212
+ const supportedServiceVersion = options.services[service];
213
+
214
+ if (!supportedServiceVersion) {
215
+ throw new Error(`Unsupported service '${service}'`);
216
+ }
217
+
218
+ if (serviceVersion !== supportedServiceVersion) {
219
+ throw new Error(
220
+ `Unsupported version '${serviceVersion}' of service '${service}'`,
221
+ );
222
+ }
223
+ }
224
+
225
+ return serialize({
226
+ network,
227
+ service,
228
+ resource: `/${resource}${search}${hash}`,
229
+ });
230
+ }