@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/LICENSE +399 -0
- package/README.md +103 -0
- package/dist/constants.d.ts +19 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +20 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +34 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/resolver.d.ts +6 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +34 -0
- package/dist/resolver.js.map +1 -0
- package/dist/resolvers/v1.d.ts +3 -0
- package/dist/resolvers/v1.d.ts.map +1 -0
- package/dist/resolvers/v1.js +109 -0
- package/dist/resolvers/v1.js.map +1 -0
- package/dist/util.d.ts +3 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +13 -0
- package/dist/util.js.map +1 -0
- package/dist/validator.d.ts +2 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +35 -0
- package/dist/validator.js.map +1 -0
- package/package.json +34 -0
- package/src/index.ts +230 -0
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 @@
|
|
|
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"}
|
package/dist/resolver.js
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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
|
package/dist/util.js.map
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|