@inrupt/solid-client
Version:
Make your web apps work with Solid Pods.
1,126 lines (1,117 loc) • 513 kB
JavaScript
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