UNPKG

@inrupt/solid-client

Version:

Make your web apps work with Solid Pods.

1,126 lines (1,117 loc) • 513 kB
import { JsonLdParser } from 'jsonld-streaming-parser'; import { FetchDocumentLoader } from 'jsonld-context-parser'; import { ClientHttpError } from '@inrupt/solid-client-errors'; import LinkHeader from 'http-link-header'; import { Store, DataFactory, Parser, Writer, NamedNode } from 'n3'; import { v4 } from 'uuid'; // Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // /** * Verify whether a given SolidDataset includes metadata about where it was sent to. * * @param dataset A [[SolidDataset]] that may have metadata attached about the Resource it was retrieved from. * @returns True if `dataset` includes metadata about the Resource it was sent to, false if not. * @since 0.2.0 */ function hasResourceInfo(resource) { const potentialResourceInfo = resource; return (typeof potentialResourceInfo === "object" && typeof potentialResourceInfo.internal_resourceInfo === "object"); } /** * Verify whether a given SolidDataset includes metadata about where it was retrieved from. * * @param dataset A [[SolidDataset]] that may have metadata attached about the Resource it was retrieved from. * @returns True if `dataset` includes metadata about the Resource it was retrieved from, false if not. * @since 0.6.0 */ function hasServerResourceInfo(resource) { const potentialResourceInfo = resource; return (typeof potentialResourceInfo === "object" && typeof potentialResourceInfo.internal_resourceInfo === "object" && typeof potentialResourceInfo.internal_resourceInfo.linkedResources === "object"); } /** @internal */ function hasChangelog(dataset) { const potentialChangeLog = dataset; return (typeof potentialChangeLog.internal_changeLog === "object" && Array.isArray(potentialChangeLog.internal_changeLog.additions) && Array.isArray(potentialChangeLog.internal_changeLog.deletions)); } /** * Errors thrown by solid-client extend this class, and can thereby be distinguished from errors * thrown in lower-level libraries. * @since 1.2.0 */ class SolidClientError extends Error { } // Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // /** @internal */ function internal_toIriString(iri) { return typeof iri === "string" ? iri : iri.value; } /** * @hidden * @param inputUrl The URL to normalize * @param options If trailingSlash is set, a trailing slash will be respectively added/removed. * The input URL trailing slash is left unchanged if trailingSlash is undefined. * @returns the normalized URL, without relative components, slash sequences, and proper trailing slash. */ function normalizeUrl(inputUrl, options = {}) { // Normalize relative components. const normalizedUrl = new URL(inputUrl); // Collapse slash sequences. normalizedUrl.pathname = normalizedUrl.pathname.replace(/\/\/+/g, "/"); // Enforce a trailing slash is present/absent. if (options.trailingSlash === false && normalizedUrl.pathname.slice(-1) === "/") { normalizedUrl.pathname = normalizedUrl.pathname.slice(0, normalizedUrl.pathname.length - 1); } if (options.trailingSlash === true && normalizedUrl.pathname.slice(-1) !== "/") { normalizedUrl.pathname = `${normalizedUrl.pathname}/`; } return normalizedUrl.href; } // Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // /** * @internal */ function internal_parseResourceInfo(response) { var _a, _b, _c, _d, _e; const contentTypeParts = (_b = (_a = response.headers.get("Content-Type")) === null || _a === void 0 ? void 0 : _a.split(";")) !== null && _b !== void 0 ? _b : []; // If the server offers a Turtle or JSON-LD serialisation on its own accord, // that tells us whether it is RDF data that the server can understand // (and hence can be updated with a PATCH request with SPARQL INSERT and DELETE statements), // in which case our SolidDataset-related functions should handle it. // For more context, see https://github.com/inrupt/solid-client-js/pull/214. const isSolidDataset = contentTypeParts.length > 0 && ["text/turtle", "application/ld+json"].includes(contentTypeParts[0]); const resourceInfo = { sourceIri: response.url, isRawData: !isSolidDataset, contentLocation: (_c = response.headers.get("Content-Location")) !== null && _c !== void 0 ? _c : undefined, contentType: (_d = response.headers.get("Content-Type")) !== null && _d !== void 0 ? _d : undefined, linkedResources: {}, location: (_e = response.headers.get("Location")) !== null && _e !== void 0 ? _e : undefined, }; const linkHeader = response.headers.get("Link"); if (linkHeader) { const parsedLinks = LinkHeader.parse(linkHeader); // Set ACL link const aclLinks = parsedLinks.get("rel", "acl"); if (aclLinks.length === 1) { resourceInfo.aclUrl = new URL(aclLinks[0].uri, resourceInfo.sourceIri).href; } // Parse all link headers and expose them in a standard way // (this can replace the parsing of the ACL link above): resourceInfo.linkedResources = parsedLinks.refs.reduce((rels, ref) => { var _a; var _b; (_a = rels[_b = ref.rel]) !== null && _a !== void 0 ? _a : (rels[_b] = []); rels[ref.rel].push(new URL(ref.uri, resourceInfo.sourceIri).href); return rels; }, resourceInfo.linkedResources); } const wacAllowHeader = response.headers.get("WAC-Allow"); if (wacAllowHeader) { resourceInfo.permissions = parseWacAllowHeader(wacAllowHeader); } return resourceInfo; } /** * Parse a WAC-Allow header into user and public access booleans. * * @param wacAllowHeader A WAC-Allow header in the format `user="read append write control",public="read"` * @see https://github.com/solid/solid-spec/blob/cb1373a369398d561b909009bd0e5a8c3fec953b/api-rest.md#wac-allow-headers */ function parseWacAllowHeader(wacAllowHeader) { function parsePermissionStatement(permissionStatement) { const permissions = permissionStatement.split(" "); const writePermission = permissions.includes("write"); return writePermission ? { read: permissions.includes("read"), append: true, write: true, control: permissions.includes("control"), } : { read: permissions.includes("read"), append: permissions.includes("append"), write: false, control: permissions.includes("control"), }; } function getStatementFor(header, scope) { const relevantEntries = header .split(",") .map((rawEntry) => rawEntry.split("=")) .filter((parts) => parts.length === 2 && parts[0].trim() === scope); // There should only be one statement with the given scope: if (relevantEntries.length !== 1) { return ""; } const relevantStatement = relevantEntries[0][1].trim(); // The given statement should be wrapped in double quotes to be valid: if (relevantStatement.charAt(0) !== '"' || relevantStatement.charAt(relevantStatement.length - 1) !== '"') { return ""; } // Return the statment without the wrapping quotes, e.g.: read append write control return relevantStatement.substring(1, relevantStatement.length - 1); } return { user: parsePermissionStatement(getStatementFor(wacAllowHeader, "user")), public: parsePermissionStatement(getStatementFor(wacAllowHeader, "public")), }; } /** @hidden Used to instantiate a separate instance from input parameters */ function internal_cloneResource(resource) { let clonedResource; if (typeof resource.slice === "function") { // If given Resource is a File: clonedResource = Object.assign(resource.slice(), { ...resource }); } else { // If it is just a plain object containing metadata: clonedResource = { ...resource }; } return clonedResource; } /** @internal */ function internal_isUnsuccessfulResponse(response) { return !response.ok; } function internal_isAuthenticationFailureResponse(response) { return response.status === 401 || response.status === 403; } // Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // TODO: These should be replaced by auto-generated constants, // if we can ensure that unused constants will be excluded from bundles. /** @hidden */ const acl = { Authorization: "http://www.w3.org/ns/auth/acl#Authorization", AuthenticatedAgent: "http://www.w3.org/ns/auth/acl#AuthenticatedAgent", accessTo: "http://www.w3.org/ns/auth/acl#accessTo", agent: "http://www.w3.org/ns/auth/acl#agent", agentGroup: "http://www.w3.org/ns/auth/acl#agentGroup", agentClass: "http://www.w3.org/ns/auth/acl#agentClass", default: "http://www.w3.org/ns/auth/acl#default", defaultForNew: "http://www.w3.org/ns/auth/acl#defaultForNew", mode: "http://www.w3.org/ns/auth/acl#mode", origin: "http://www.w3.org/ns/auth/acl#origin", }; /** @hidden */ const rdf = { type: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", }; /** @hidden */ const rdfs = { seeAlso: "http://www.w3.org/2000/01/rdf-schema#seeAlso", }; /** @hidden */ const ldp = { BasicContainer: "http://www.w3.org/ns/ldp#BasicContainer", Container: "http://www.w3.org/ns/ldp#Container", Resource: "http://www.w3.org/ns/ldp#Resource", contains: "http://www.w3.org/ns/ldp#contains", }; /** @hidden */ const foaf = { Agent: "http://xmlns.com/foaf/0.1/Agent", primaryTopic: "http://xmlns.com/foaf/0.1/primaryTopic", isPrimaryTopicOf: "http://xmlns.com/foaf/0.1/isPrimaryTopicOf", }; /** @hidden */ const acp = { AccessControlResource: "http://www.w3.org/ns/solid/acp#AccessControlResource", Policy: "http://www.w3.org/ns/solid/acp#Policy", AccessControl: "http://www.w3.org/ns/solid/acp#AccessControl", Read: "http://www.w3.org/ns/solid/acp#Read", Append: "http://www.w3.org/ns/solid/acp#Append", Write: "http://www.w3.org/ns/solid/acp#Write", /** @deprecated Removed from the ACP proposal, to be replaced by Matchers. */ Rule: "http://www.w3.org/ns/solid/acp#Rule", Matcher: "http://www.w3.org/ns/solid/acp#Matcher", accessControl: "http://www.w3.org/ns/solid/acp#accessControl", memberAccessControl: "http://www.w3.org/ns/solid/acp#memberAccessControl", apply: "http://www.w3.org/ns/solid/acp#apply", /** @deprecated Removed from the ACP proposal, to be replaced by memberAccessControls. */ applyMembers: "http://www.w3.org/ns/solid/acp#applyMembers", allow: "http://www.w3.org/ns/solid/acp#allow", deny: "http://www.w3.org/ns/solid/acp#deny", allOf: "http://www.w3.org/ns/solid/acp#allOf", anyOf: "http://www.w3.org/ns/solid/acp#anyOf", noneOf: "http://www.w3.org/ns/solid/acp#noneOf", access: "http://www.w3.org/ns/solid/acp#access", /** @deprecated Removed from the ACP proposal, to be replaced by memberAccessControls. */ accessMembers: "http://www.w3.org/ns/solid/acp#accessMembers", agent: "http://www.w3.org/ns/solid/acp#agent", group: "http://www.w3.org/ns/solid/acp#group", client: "http://www.w3.org/ns/solid/acp#client", PublicAgent: "http://www.w3.org/ns/solid/acp#PublicAgent", AuthenticatedAgent: "http://www.w3.org/ns/solid/acp#AuthenticatedAgent", CreatorAgent: "http://www.w3.org/ns/solid/acp#CreatorAgent", }; /** @hidden */ const solid = { PublicOidcClient: "http://www.w3.org/ns/solid/terms#PublicOidcClient", }; /** @hidden */ const security = { publicKey: "https://w3id.org/security#publicKey", }; /** @hidden */ const pim = { storage: "http://www.w3.org/ns/pim/space#storage", }; // Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // This linter exception is introduced for legacy reasons. /** * Retrieve the information about a resource (e.g. access permissions) without * fetching the resource itself. * * @param url URL to fetch Resource metadata from. * @param options Optional parameter `options.fetch`: An alternative `fetch` function to make the HTTP request, compatible with the browser-native [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters). * @returns Promise resolving to the metadata describing the given Resource, or rejecting if fetching it failed. * @since 0.4.0 */ async function getResourceInfo(url, options) { var _a, _b; const response = await ((_a = options === null || options === void 0 ? void 0 : options.fetch) !== null && _a !== void 0 ? _a : fetch)(normalizeUrl(url), { method: "HEAD", }); return responseToResourceInfo(response, { ignoreAuthenticationErrors: (_b = options === null || options === void 0 ? void 0 : options.ignoreAuthenticationErrors) !== null && _b !== void 0 ? _b : false, }); } /** * Parse Solid metadata from a Response obtained by fetching a Resource from a Solid Pod, * * @param response A Fetch API Response. See {@link https://developer.mozilla.org/en-US/docs/Web/API/Response MDN}. * @returns Resource metadata readable by functions such as [[getSourceUrl]]. * @hidden This interface is not exposed yet until we've tried it out in practice. */ function responseToResourceInfo(response, options = { ignoreAuthenticationErrors: false }) { if (internal_isUnsuccessfulResponse(response) && (!internal_isAuthenticationFailureResponse(response) || !options.ignoreAuthenticationErrors)) { throw new FetchError(`Fetching the metadata of the Resource at [${response.url}] failed: [${response.status}] [${response.statusText}].`, response); } const resourceInfo = internal_parseResourceInfo(response); return { internal_resourceInfo: resourceInfo }; } /** * @param resource Resource for which to check whether it is a Container. * @returns Whether `resource` is a Container. */ function isContainer(resource) { const containerUrl = hasResourceInfo(resource) ? getSourceUrl(resource) : internal_toIriString(resource); return containerUrl.endsWith("/"); } /** * This function will tell you whether a given Resource contains raw data, or a SolidDataset. * * @param resource Resource for which to check whether it contains raw data. * @return Whether `resource` contains raw data. */ function isRawData(resource) { return resource.internal_resourceInfo.isRawData; } /** * @param resource Resource for which to determine the Content Type. * @returns The Content Type, if known, or null if not known. */ function getContentType$1(resource) { var _a; return (_a = resource.internal_resourceInfo.contentType) !== null && _a !== void 0 ? _a : null; } function getSourceUrl(resource) { if (hasResourceInfo(resource)) { return resource.internal_resourceInfo.sourceIri; } return null; } /** @hidden Alias of getSourceUrl for those who prefer to use IRI terminology. */ const getSourceIri = getSourceUrl; /** * Given a Resource that exposes information about the owner of the Pod it is in, returns the WebID of that owner. * * Data about the owner of the Pod is exposed when the following conditions hold: * - The Pod server supports exposing the Pod owner * - The current user is allowed to see who the Pod owner is. * * If one or more of those conditions are false, this function will return `null`. * * @param resource A Resource that contains information about the owner of the Pod it is in. * @returns The WebID of the owner of the Pod the Resource is in, if provided, or `null` if not. * @since 0.6.0 */ function getPodOwner(resource) { var _a; if (!hasServerResourceInfo(resource)) { return null; } const podOwners = (_a = getLinkedResourceUrlAll(resource)["http://www.w3.org/ns/solid/terms#podOwner"]) !== null && _a !== void 0 ? _a : []; return podOwners.length === 1 ? podOwners[0] : null; } /** * Given a WebID and a Resource that exposes information about the owner of the Pod it is in, returns whether the given WebID is the owner of the Pod. * * Data about the owner of the Pod is exposed when the following conditions hold: * - The Pod server supports exposing the Pod owner * - The current user is allowed to see who the Pod owner is. * * If one or more of those conditions are false, this function will return `null`. * * @param webId The WebID of which to check whether it is the Pod Owner's. * @param resource A Resource that contains information about the owner of the Pod it is in. * @returns Whether the given WebID is the Pod Owner's, if the Pod Owner is exposed, or `null` if it is not exposed. * @since 0.6.0 */ function isPodOwner(webId, resource) { const podOwner = getPodOwner(resource); if (typeof podOwner !== "string") { return null; } return podOwner === webId; } /** * Get the URLs of Resources linked to the given Resource. * * Solid servers can link Resources to each other. For example, in servers * implementing Web Access Control, Resources can have an Access Control List * Resource linked to it via the `acl` relation. * * @param resource A Resource fetched from a Solid Pod. * @returns The URLs of Resources linked to the given Resource, indexed by the key that links them. * @since 1.7.0 */ function getLinkedResourceUrlAll(resource) { return resource.internal_resourceInfo.linkedResources; } /** * Get what access the current user has to the given Resource. * * This function can tell you what access the current user has for the given * Resource, allowing you to e.g. determine that changes to it will be rejected * before attempting to do so. * Additionally, for servers adhering to the Web Access Control specification, * it will tell you what access unauthenticated users have to the given Resource. * * @param resource A Resource fetched from a Solid Pod. * @returns What access the current user and, if supported by the server, unauthenticated users have to the given Resource. * @since 1.7.0 */ function getEffectiveAccess(resource) { var _a, _b, _c, _d, _e, _f, _g; if (typeof resource.internal_resourceInfo.permissions === "object") { return { user: { read: resource.internal_resourceInfo.permissions.user.read, append: resource.internal_resourceInfo.permissions.user.append, write: resource.internal_resourceInfo.permissions.user.write, }, public: { read: resource.internal_resourceInfo.permissions.public.read, append: resource.internal_resourceInfo.permissions.public.append, write: resource.internal_resourceInfo.permissions.public.write, }, }; } const linkedResourceUrls = getLinkedResourceUrlAll(resource); return { user: { read: (_b = (_a = linkedResourceUrls[acp.allow]) === null || _a === void 0 ? void 0 : _a.includes(acp.Read)) !== null && _b !== void 0 ? _b : false, append: (_e = (((_c = linkedResourceUrls[acp.allow]) === null || _c === void 0 ? void 0 : _c.includes(acp.Append)) || ((_d = linkedResourceUrls[acp.allow]) === null || _d === void 0 ? void 0 : _d.includes(acp.Write)))) !== null && _e !== void 0 ? _e : false, write: (_g = (_f = linkedResourceUrls[acp.allow]) === null || _f === void 0 ? void 0 : _f.includes(acp.Write)) !== null && _g !== void 0 ? _g : false, }, }; } /** * Extends the regular JavaScript error object with access to the status code and status message. * @since 1.2.0 */ class FetchError extends SolidClientError { constructor(message, errorResponse, responseBody) { super(message); this.response = errorResponse; if (typeof responseBody === "string") { this.httpError = new ClientHttpError(errorResponse, responseBody, message); } else { // If no response body is provided, defaults are applied. this.httpError = new ClientHttpError(errorResponse, "", message); } } get statusCode() { return this.response.status; } get statusText() { return this.response.statusText; } get problemDetails() { return this.httpError.problemDetails; } } // Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // /** * ```{note} This function is still experimental and subject to change, even * in a non-major release. * ``` * This returns a parser that transforms a JSON-LD string into a set of RDFJS quads. * * @returns A Parser object. * @since 1.15.0 */ const getJsonLdParser$1 = () => { const onQuadCallbacks = []; const onCompleteCallbacks = []; const onErrorCallbacks = []; return { onQuad: (callback) => { onQuadCallbacks.push(callback); }, onError: (callback) => { onErrorCallbacks.push(callback); }, onComplete: (callback) => { onCompleteCallbacks.push(callback); }, // The following returns a Promise that can be awaited, which is undocumented // behavior that doesn't match the type signature. It prevents a potentially // breaking change, and will be updated on the next major release. parse: (source, resourceInfo) => new Promise((res) => { const parser = new JsonLdParser({ baseIRI: getSourceUrl(resourceInfo), documentLoader: new FetchDocumentLoader((...args) => fetch(...args)), }); let endCalled = false; function end() { if (!endCalled) { endCalled = true; onCompleteCallbacks.forEach((callback) => callback()); res(); } } parser.on("end", end); parser.on("error", (err) => { onErrorCallbacks.forEach((callback) => callback(err)); end(); }); onQuadCallbacks.forEach((callback) => parser.on("data", callback)); parser.write(source); parser.end(); }), }; }; // Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // const rdfJsDataset = (quads) => new Store(quads); const localNodeSkolemPrefix = "https://inrupt.com/.well-known/sdk-local-node/"; /** * Runtime freezing might be too much overhead; * if so, this function allows us to replace it by a function * that merely marks its input as Readonly<> for static analysis. */ const { freeze } = Object; function isLocalNodeIri(iri) { return (iri.substring(0, localNodeSkolemPrefix.length) === localNodeSkolemPrefix); } function getLocalNodeName(localNodeIri) { return localNodeIri.substring(localNodeSkolemPrefix.length); } function getLocalNodeIri(localNodeName) { return `${localNodeSkolemPrefix}${localNodeName}`; } function isBlankNodeId(value) { return typeof value === "string" && value.substring(0, 2) === "_:"; } function getBlankNodeValue(blankNodeId) { return blankNodeId.substring(2); } function getBlankNodeId(blankNode) { return `_:${blankNode.value}`; } // Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // /** * IRIs of the XML Schema data types we support */ const xmlSchemaTypes = { boolean: "http://www.w3.org/2001/XMLSchema#boolean", dateTime: "http://www.w3.org/2001/XMLSchema#dateTime", date: "http://www.w3.org/2001/XMLSchema#date", time: "http://www.w3.org/2001/XMLSchema#time", decimal: "http://www.w3.org/2001/XMLSchema#decimal", integer: "http://www.w3.org/2001/XMLSchema#integer", string: "http://www.w3.org/2001/XMLSchema#string", langString: "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString", }; /** * @internal * @param value Value to serialise. * @returns String representation of `value`. * @see https://www.w3.org/TR/xmlschema-2/#boolean-lexical-representation */ function serializeBoolean(value) { return value ? "true" : "false"; } /** * @internal * @param value Value to deserialise. * @returns Deserialized boolean, or null if the given value is not a valid serialised boolean. * @see https://www.w3.org/TR/xmlschema-2/#boolean-lexical-representation */ function deserializeBoolean(value) { if (value === "true" || value === "1") { return true; } if (value === "false" || value === "0") { return false; } return null; } /** * @internal * @param value Value to serialise. * @returns String representation of `value` in UTC. * @see https://www.w3.org/TR/xmlschema-2/#time-lexical-repr */ function serializeTime(value) { let millisecondString; let timezoneString; if (value.millisecond) { if (value.millisecond < 10) { millisecondString = `00${value.millisecond}`; } else if (value.millisecond < 100) { millisecondString = `0${value.millisecond}`; } else { millisecondString = value.millisecond; } } if (typeof value.timezoneHourOffset === "number") { const timezoneFormatted = Math.abs(value.timezoneHourOffset) < 10 ? `0${Math.abs(value.timezoneHourOffset)}` : Math.abs(value.timezoneHourOffset); timezoneString = value.timezoneHourOffset >= 0 ? `+${timezoneFormatted}` : `-${timezoneFormatted}`; if (value.timezoneMinuteOffset) { timezoneString = `${timezoneString}:${value.timezoneMinuteOffset < 10 ? `0${value.timezoneMinuteOffset}` : value.timezoneMinuteOffset}`; } else { timezoneString += ":00"; } } return `${value.hour < 10 ? `0${value.hour}` : value.hour}:${value.minute < 10 ? `0${value.minute}` : value.minute}:${value.second < 10 ? `0${value.second}` : value.second}${value.millisecond ? `.${millisecondString}` : ""}${timezoneString || ""}`; } /** * @internal * @param literalString Value to deserialise. * @returns Deserialized time, or null if the given value is not a valid serialised datetime. * @see https://www.w3.org/TR/xmlschema-2/#time-lexical-repr */ function deserializeTime(literalString) { // Time in the format described at // https://www.w3.org/TR/xmlschema-2/#time-lexical-repr // \d\d:\d\d:\d\d - Two digits for the hour, minute and second, respectively, separated by a `:`. // Example: "13:37:42". // (\.\d+)? - Optionally a `.` followed by one or more digits representing milliseconds. // Example: ".1337". // (Z|(\+|-)\d\d:\d\d) - The letter Z indicating UTC, or a `+` or `-` followed by two digits for // the hour offset and two for the minute offset, separated by a `:`. // Example: "+13:37". const timeRegEx = /\d\d:\d\d:\d\d(\.\d+)?(Z|(\+|-)\d\d:\d\d)?/; if (!timeRegEx.test(literalString)) { return null; } const [timeString, timezoneString] = splitTimeFromTimezone(literalString); const [hourString, minuteString, timeRest] = timeString.split(":"); let utcHours = Number.parseInt(hourString, 10); let utcMinutes = Number.parseInt(minuteString, 10); const [secondString, optionalMillisecondString] = timeRest.split("."); const utcSeconds = Number.parseInt(secondString, 10); const utcMilliseconds = optionalMillisecondString ? Number.parseInt(optionalMillisecondString, 10) : undefined; if (utcMinutes >= 60) { utcHours += 1; utcMinutes -= 60; } const deserializedTime = { hour: utcHours, minute: utcMinutes, second: utcSeconds, }; if (typeof utcMilliseconds === "number") { deserializedTime.millisecond = utcMilliseconds; } if (typeof timezoneString === "string") { const [hourOffset, minuteOffset] = getTimezoneOffsets(timezoneString); if (typeof hourOffset !== "number" || hourOffset > 24 || typeof minuteOffset !== "number" || minuteOffset > 59) { return null; } deserializedTime.timezoneHourOffset = hourOffset; deserializedTime.timezoneMinuteOffset = minuteOffset; } return deserializedTime; } /** * @internal * @param value Value to serialise. * @returns String representation of `value`. * @see https://www.w3.org/TR/xmlschema-2/#dateTime-lexical-representation */ function serializeDatetime(value) { // Although the XML Schema DateTime is not _exactly_ an ISO 8601 string // (see https://www.w3.org/TR/xmlschema-2/#deviantformats), // the deviations only affect the parsing, not the serialisation. // Therefore, we can just use .toISOString(): return value.toISOString(); } /** * @internal * @param value Value to deserialise. * @returns Deserialized datetime, or null if the given value is not a valid serialised datetime. * @see https://www.w3.org/TR/xmlschema-2/#dateTime-lexical-representation */ function deserializeDatetime(literalString) { // DateTime in the format described at // https://www.w3.org/TR/xmlschema-2/#dateTime-lexical-representation // (without constraints on the value). // -? - An optional leading `-`. // \d{4,}- - Four or more digits followed by a `-` representing the year. Example: "3000-". // \d\d-\d\d - Two digits representing the month and two representing the day of the month, // separated by a `-`. Example: "11-03". // T - The letter T, separating the date from the time. // \d\d:\d\d:\d\d - Two digits for the hour, minute and second, respectively, separated by a `:`. // Example: "13:37:42". // (\.\d+)? - Optionally a `.` followed by one or more digits representing milliseconds. // Example: ".1337". // (Z|(\+|-)\d\d:\d\d) - The letter Z indicating UTC, or a `+` or `-` followed by two digits for // the hour offset and two for the minute offset, separated by a `:`. // Example: "+13:37". const datetimeRegEx = /-?\d{4,}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(Z|(\+|-)\d\d:\d\d)?/; if (!datetimeRegEx.test(literalString)) { return null; } const [signedDateString, rest] = literalString.split("T"); // The date string can optionally be prefixed with `-`, // in which case the year is negative: const [yearMultiplier, dateString] = signedDateString.charAt(0) === "-" ? [-1, signedDateString.substring(1)] : [1, signedDateString]; const [yearString, monthString, dayString] = dateString.split("-"); const utcFullYear = Number.parseInt(yearString, 10) * yearMultiplier; const utcMonth = Number.parseInt(monthString, 10) - 1; const utcDate = Number.parseInt(dayString, 10); const [timeString, timezoneString] = splitTimeFromTimezone(rest); const [hourOffset, minuteOffset] = typeof timezoneString === "string" ? getTimezoneOffsets(timezoneString) : [0, 0]; const [hourString, minuteString, timeRest] = timeString.split(":"); const utcHours = Number.parseInt(hourString, 10) + hourOffset; const utcMinutes = Number.parseInt(minuteString, 10) + minuteOffset; const [secondString, optionalMillisecondString] = timeRest.split("."); const utcSeconds = Number.parseInt(secondString, 10); const utcMilliseconds = optionalMillisecondString ? Number.parseInt(optionalMillisecondString, 10) : 0; const date = new Date(Date.UTC(utcFullYear, utcMonth, utcDate, utcHours, utcMinutes, utcSeconds, utcMilliseconds)); // For the year, values from 0 to 99 map to the years 1900 to 1999. Since the serialisation // always writes out the years fully, we should correct this to actually map to the years 0 to 99. // See // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#Individual_date_and_time_component_values if (utcFullYear >= 0 && utcFullYear < 100) { // Note that we base it on the calculated year, rather than the year that was actually read. // This is because the year might actually differ from the value listed in the serialisation, // i.e. when moving the timezone offset to UTC pushes it into a different year: date.setUTCFullYear(date.getUTCFullYear() - 1900); } return date; } /** * @internal * @param value Value to serialise. * @returns String representation of `value`. * @see https://www.w3.org/TR/xmlschema-2/#date-lexical-representation */ function serializeDate(value) { const year = value.getFullYear(); const month = value.getMonth() + 1; const day = value.getDate(); const [, timezone] = splitTimeFromTimezone(value.toISOString()); return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}${timezone}`; } /** * @internal * @param value Value to deserialise. * @returns Deserialized datetime, or null if the given value is not a valid serialised datetime. * @see https://www.w3.org/TR/xmlschema-2/#date-lexical-representation */ function deserializeDate(literalString) { // Date in the format described at // https://www.w3.org/TR/xmlschema-2/#date-lexical-representation // (without constraints on the value). // -? - An optional leading `-`. // \d{4,}- - Four or more digits followed by a `-` representing the year. Example: "3000-". // \d\d-\d\d - Two digits representing the month and two representing the day of the month, // separated by a `-`. Example: "11-03". // (Z|(\+|-)\d\d:\d\d) - Optionally, the letter Z indicating UTC, or a `+` or `-` followed by two digits for // the hour offset and two for the minute offset, separated by a `:`. // Example: "+13:37". const dateRegEx = /-?\d{4,}-\d\d-\d\d(Z|(\+|-)\d\d:\d\d)?/; if (!dateRegEx.test(literalString)) { return null; } const signedDateString = literalString; // The date string can optionally be prefixed with `-`, // in which case the year is negative: const [yearMultiplier, dateString] = signedDateString.charAt(0) === "-" ? [-1, signedDateString.substring(1)] : [1, signedDateString]; const [yearString, monthString, dayAndTimezoneString] = dateString.split("-"); const dayString = dayAndTimezoneString.length > 2 ? dayAndTimezoneString.substring(0, 2) : dayAndTimezoneString; const utcFullYear = Number.parseInt(yearString, 10) * yearMultiplier; const utcMonth = Number.parseInt(monthString, 10) - 1; const utcDate = Number.parseInt(dayString, 10); const hour = 12; // setting at 12:00 avoids all timezones const date = new Date(Date.UTC(utcFullYear, utcMonth, utcDate, hour)); // For the year, values from 0 to 99 map to the years 1900 to 1999. Since the serialisation // always writes out the years fully, we should correct this to actually map to the years 0 to 99. // See // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#Individual_date_and_time_component_values if (utcFullYear >= 0 && utcFullYear < 100) { date.setUTCFullYear(date.getUTCFullYear() - 1900); } return date; } /** * @param timeString An XML Schema time string. * @returns A tuple [timeString, timezoneString]. * @see https://www.w3.org/TR/xmlschema-2/#time-lexical-repr */ function splitTimeFromTimezone(timeString) { if (timeString.endsWith("Z")) { return [timeString.substring(0, timeString.length - 1), "Z"]; } const splitOnPlus = timeString.split("+"); const splitOnMinus = timeString.split("-"); if (splitOnPlus.length === 1 && splitOnMinus.length === 1) { return [splitOnPlus[0], undefined]; } return splitOnPlus.length > splitOnMinus.length ? [splitOnPlus[0], `+${splitOnPlus[1]}`] : [splitOnMinus[0], `-${splitOnMinus[1]}`]; } /** * @param timezoneString Lexical representation of a time zone in XML Schema. * @returns A tuple of the hour and minute offset of the time zone. * @see https://www.w3.org/TR/xmlschema-2/#dateTime-timezones */ function getTimezoneOffsets(timezoneString) { if (timezoneString === "Z") { return [0, 0]; } const multiplier = timezoneString.charAt(0) === "+" ? 1 : -1; const [hourString, minuteString] = timezoneString.substring(1).split(":"); const hours = Number.parseInt(hourString, 10); const minutes = Number.parseInt(minuteString, 10); return [hours * multiplier, minutes * multiplier]; } /** * @internal * @param value Value to serialise. * @returns String representation of `value`. * @see https://www.w3.org/TR/xmlschema-2/#decimal-lexical-representation */ function serializeDecimal(value) { return value.toString(); } /** * @internal * @param value Value to deserialise. * @returns Deserialized decimal, or null if the given value is not a valid serialised decimal. * @see https://www.w3.org/TR/xmlschema-2/#decimal-lexical-representation */ function deserializeDecimal(literalString) { const deserialized = Number.parseFloat(literalString); if (Number.isNaN(deserialized)) { return null; } return deserialized; } /** * @internal * @param value Value to serialise. * @returns String representation of `value`. */ function serializeInteger(value) { return value.toString(); } /** * @internal * @param value Value to deserialise. * @returns Deserialized integer, or null if the given value is not a valid serialised integer. */ function deserializeInteger(literalString) { const deserialized = Number.parseInt(literalString, 10); if (Number.isNaN(deserialized)) { return null; } return deserialized; } /** * @internal * @param locale Locale to transform into a consistent format. */ function normalizeLocale(locale) { return locale.toLowerCase(); } /** * @internal Library users shouldn't need to be exposed to raw NamedNodes. * @param value The value that might or might not be a Named Node. * @returns Whether `value` is a Named Node. */ function isNamedNode(value) { return isTerm(value) && value.termType === "NamedNode"; } /** * @internal Library users shouldn't need to be exposed to raw Literals. * @param value The value that might or might not be a Literal. * @returns Whether `value` is a Literal. */ function isLiteral(value) { return isTerm(value) && value.termType === "Literal"; } /** * @internal Library users shouldn't need to be exposed to raw Terms. * @param value The value that might or might not be a Term. * @returns Whether `value` is a Term. */ function isTerm(value) { return (value !== null && typeof value === "object" && typeof value.termType === "string" && typeof value.value === "string" && typeof value.equals === "function"); } /** * @internal Library users shouldn't need to be exposed to LocalNodes. * @param value The value that might or might not be a Node with no known IRI yet. * @returns Whether `value` is a Node with no known IRI yet. */ function isLocalNode(value) { return isNamedNode(value) && isLocalNodeIri(value.value); } /** * Ensure that a given value is a valid URL. * * @internal Library users shouldn't need to be exposed to raw URLs. * @param iri The value of which to verify that it is a valid URL. */ function internal_isValidUrl(iri) { const iriString = internal_toIriString(iri); // If the runtime environment supports URL, instantiate one. // If the given IRI is not a valid URL, it will throw an error. // See: https://developer.mozilla.org/en-US/docs/Web/API/URL /* istanbul ignore if [URL is available in our testing environment, so we cannot test the alternative] */ if (typeof URL !== "function") { // If we can't validate the URL, do not throw an error: return true; } try { new URL(iriString); return true; } catch (_a) { return false; } } /** * @internal Utility method; library users should not need to interact with LocalNodes directly. * @param localNode The LocalNode to resolve to a NamedNode. * @param resourceIri The Resource in which the Node will be saved. */ function resolveIriForLocalNode(localNode, resourceIri) { return DataFactory.namedNode(resolveLocalIri(getLocalNodeName(localNode.value), resourceIri)); } /** * @internal API for internal use only. * @param name The name identifying a Thing. * @param resourceIri The Resource in which the Thing can be found. */ function resolveLocalIri(name, resourceIri) { /* istanbul ignore if [The URL interface is available in the testing environment, so we cannot test this] */ if (typeof URL !== "function") { throw new Error("The URL interface is not available, so an IRI cannot be determined."); } const thingIri = new URL(resourceIri); thingIri.hash = name; return thingIri.href; } // Copyright Inrupt Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Softwar