diff --git a/CHANGELOG.md b/CHANGELOG.md index 1389445ec3..94f3c41609 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -240,7 +240,7 @@ The following changes have been implemented but not released yet: ### Bugfixes -- `getWellKnownSolid` fetches well known solid from the server's root by default. +- `getWellKnownSolid` fetches well-known solid from the server's root by default. ## [1.22.0] - 2022-04-26 diff --git a/src/resource/__snapshots__/solidDataset.test.ts.snap b/src/resource/__snapshots__/solidDataset.test.ts.snap index b349d4eaa3..a82de9955e 100644 --- a/src/resource/__snapshots__/solidDataset.test.ts.snap +++ b/src/resource/__snapshots__/solidDataset.test.ts.snap @@ -57,55 +57,11 @@ exports[`getSolidDataset returns a SolidDataset representing the fetched Turtle } `; -exports[`getWellKnownSolid returns the contents of .well-known/solid for the given resource (1.1) 1`] = ` +exports[`getWellKnownSolid returns the contents of .well-known/solid for the given resource 1`] = ` { "graphs": { "default": { - "_:b0": { - "predicates": { - "http://inrupt.com/ns/ess#consentIssuer": { - "namedNodes": [ - "https://consent.pod.inrupt.com", - ], - }, - "http://inrupt.com/ns/ess#notificationGatewayEndpoint": { - "namedNodes": [ - "https://notification.pod.inrupt.com", - ], - }, - "http://inrupt.com/ns/ess#powerSwitchEndpoint": { - "namedNodes": [ - "https://pod.inrupt.com/powerswitch/username", - ], - }, - "http://www.w3.org/ns/pim/space#storage": { - "namedNodes": [ - "https://pod.inrupt.com/username/", - ], - }, - }, - "type": "Subject", - "url": "_:b0", - }, - }, - }, - "internal_resourceInfo": { - "contentLocation": undefined, - "contentType": "application/ld+json", - "isRawData": false, - "linkedResources": {}, - "location": undefined, - "sourceIri": "https://example.org/pod/.well-known/solid", - }, - "type": "Dataset", -} -`; - -exports[`getWellKnownSolid returns the contents of .well-known/solid for the given resource (2.0) 1`] = ` -{ - "graphs": { - "default": { - "_:n3-3": { + "_:n3-2": { "predicates": { "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": { "namedNodes": [ @@ -114,7 +70,7 @@ exports[`getWellKnownSolid returns the contents of .well-known/solid for the giv }, "http://www.w3.org/ns/auth/acl#trustedApp": { "namedNodes": [ - "https://podbrowser.inrupt.com/api/app", + "https://inrupt.com", ], }, "http://www.w3.org/ns/solid/terms#maxPodsPerOwner": { @@ -143,7 +99,7 @@ exports[`getWellKnownSolid returns the contents of .well-known/solid for the giv }, }, "type": "Subject", - "url": "_:n3-3", + "url": "_:n3-2", }, }, }, diff --git a/src/resource/solidDataset.test.ts b/src/resource/solidDataset.test.ts index 19c77607ae..31ea683497 100644 --- a/src/resource/solidDataset.test.ts +++ b/src/resource/solidDataset.test.ts @@ -3334,18 +3334,17 @@ describe("changeLogAsMarkdown", () => { describe("getWellKnownSolid", () => { const serverUrl = "https://example.org/"; - const podUrl = "https://example.org/pod/"; const resourceUrl = "https://example.org/pod/resource"; const wellKnownSolid = ".well-known/solid"; - const ess20Implementation: typeof fetch = async () => + const essDiscoveryDoc: typeof fetch = async () => new Response( `@prefix solid: . [ a solid:DiscoveryDocument ; - ; + ; solid:maxPodsPerOwner 10 ; solid:notificationGateway ; solid:provision ; @@ -3358,56 +3357,12 @@ describe("getWellKnownSolid", () => { }, ); - const ess11Implementation: typeof fetch = async ( - url: RequestInfo | URL, - init?: RequestInit, - ) => { - if (url === "https://example.org/.well-known/solid") { - return mockResponse(undefined, { status: 404 }, url); - } + const mockESS = () => + jest.spyOn(globalThis, "fetch").mockImplementation(essDiscoveryDoc); - if (url === "https://example.org/pod/resource") { - return mockResponse( - undefined, - { - headers: { - "Content-Type": "text/turtle", - link: `<${podUrl}>; rel="http://www.w3.org/ns/pim/space#storage"`, - }, - }, - resourceUrl, - ); - } - - if (url === "https://example.org/pod/.well-known/solid") { - return mockResponse( - ` - { - "@context":"https://pod.inrupt.com/solid/v1", - "consent":"https://consent.pod.inrupt.com", - "notificationGateway":"https://notification.pod.inrupt.com", - "powerSwitch":"https://pod.inrupt.com/powerswitch/username", - "storage":"https://pod.inrupt.com/username/" - }`, - { - headers: { "Content-Type": "application/ld+json" }, - }, - url, - ); - } - - throw new Error(`Unhandled request: ${url}, ${JSON.stringify(init)}`); - }; - - const mockESS20 = () => - jest.spyOn(globalThis, "fetch").mockImplementation(ess20Implementation); - - const mockESS11 = () => - jest.spyOn(globalThis, "fetch").mockImplementation(ess11Implementation); - - it("fetches root well known solid by default", async () => { - // Fetches root well known - mockESS20(); + it("fetches root well-known solid by default", async () => { + // Fetches root well-known + mockESS(); await getWellKnownSolid(resourceUrl); @@ -3418,174 +3373,25 @@ describe("getWellKnownSolid", () => { ); }); - it("uses the given fetcher for root well known solid if provided", async () => { - const mockFetch = jest.fn(ess20Implementation); - - await getWellKnownSolid(resourceUrl, { fetch: mockFetch }); - - expect(mockFetch).toHaveBeenCalledTimes(0); - // Unauthenticated fetch is still used to get the .well-known - expect(fetch).toHaveBeenCalledTimes(1); - expect(fetch).toHaveBeenCalledWith( - serverUrl.concat(wellKnownSolid), - expect.anything(), - ); - }); - - it("fetches pod root well known solid otherwise", async () => { - mockESS11(); - - await getWellKnownSolid(resourceUrl); - - expect(fetch).toHaveBeenCalledTimes(3); - // Tries the root well known solid first is used to determine well known Solid - expect(fetch).toHaveBeenNthCalledWith( - 1, - serverUrl.concat(wellKnownSolid), - expect.anything(), - ); - // Checks the resource's location header otherwise - expect(fetch).toHaveBeenNthCalledWith(2, resourceUrl, expect.anything()); - // The advertised podIdentifier (as storage) is used to determine well known Solid - expect(fetch).toHaveBeenNthCalledWith( - 3, - podUrl.concat(wellKnownSolid), - expect.anything(), - ); - }); - - it("uses the given fetcher for pod root well known solid if provided", async () => { - const mockFetch = jest.fn(ess11Implementation); - - await getWellKnownSolid(resourceUrl, { fetch: mockFetch }); - - expect(mockFetch).toHaveBeenCalledTimes(2); - expect(fetch).toHaveBeenCalledTimes(1); - // Tries the root well known solid first is used to determine well known Solid - expect(fetch).toHaveBeenNthCalledWith( - 1, - serverUrl.concat(wellKnownSolid), - expect.anything(), - ); - // Checks the resource's location header otherwise - expect(mockFetch).toHaveBeenNthCalledWith( - 1, - resourceUrl, - expect.anything(), - ); - // The advertised podIdentifier (as storage) is used to determine well known Solid - expect(mockFetch).toHaveBeenNthCalledWith( - 2, - podUrl.concat(wellKnownSolid), - expect.anything(), - ); - }); - - it("appends a / to the Pod root if missing before appending .well-known/solid", async () => { - const spyFetch = jest.spyOn(globalThis, "fetch"); - - // Root cannot be fetched - spyFetch.mockResolvedValueOnce(new Response(undefined, { status: 404 })); - // Resource advertises Pod root - spyFetch.mockResolvedValueOnce( - mockResponse( - undefined, - { - headers: { - "Content-Type": "text/turtle", - link: `; rel="http://www.w3.org/ns/pim/space#storage"`, - }, - }, - resourceUrl, - ), - ); - // Fetches Pod root well known - spyFetch.mockResolvedValueOnce( - new Response( - `{ - "@context":"https://pod.inrupt.com/solid/v1", - "consent":"https://consent.pod.inrupt.com", - "notificationGateway":"https://notification.pod.inrupt.com", - "powerSwitch":"https://pod.inrupt.com/powerswitch/username", - "storage":"https://pod.inrupt.com/username/" - }`, - { - headers: { - "Content-Type": "application/ld+json", - }, - }, - ), - ); - - await getWellKnownSolid(resourceUrl); - - expect(spyFetch.mock.calls).toHaveLength(3); - - // Tries the root well known solid first is used to determine well known Solid - expect(spyFetch.mock.calls[0][0]).toBe(serverUrl.concat(wellKnownSolid)); - // Checks the resource's location header otherwise - expect(spyFetch.mock.calls[1][0]).toBe(resourceUrl); - // The advertised podIdentifier (as storage) is used to determine well known Solid - expect(spyFetch.mock.calls[2][0]).toBe( - serverUrl.concat("username/", wellKnownSolid), - ); - }); - - it("Throws an error if the resource metadata can't be fetched", async () => { + it("Throws an error if the well-known solid resource cannot be found", async () => { const spyFetch = jest.spyOn(globalThis, "fetch"); - // Can't fetch root well known + // Can't fetch root well-known spyFetch.mockResolvedValueOnce(new Response(undefined, { status: 404 })); - // Resource advertises Pod root - spyFetch.mockResolvedValueOnce( - mockResponse( - undefined, - { - headers: { - "Content-Type": "text/turtle", - link: `; rel="http://www.w3.org/ns/pim/space#storage"`, - }, - }, - resourceUrl, - ), - ); - // Can't fetch pod root well known solid - spyFetch.mockResolvedValueOnce(new Response(undefined, { status: 404 })); - - await expect(getWellKnownSolid(resourceUrl)).rejects.toThrow(); - - expect(spyFetch.mock.calls).toHaveLength(3); - }); - - it("Throws an error if the pod root cannot be determined", async () => { - const spyFetch = jest.spyOn(globalThis, "fetch"); - - // Can't fetch root well known - spyFetch.mockResolvedValueOnce(new Response(undefined, { status: 404 })); - // Resource does not advertise pod root - spyFetch.mockResolvedValueOnce(new Response(undefined)); await expect(getWellKnownSolid(resourceUrl)).rejects.toThrow( "Could not determine storage root or well-known solid resource.", ); - expect(spyFetch.mock.calls).toHaveLength(2); + expect(spyFetch.mock.calls).toHaveLength(1); }); - it("returns the contents of .well-known/solid for the given resource (2.0)", async () => { - mockESS20(); + it("returns the contents of .well-known/solid for the given resource", async () => { + mockESS(); const wellKnownSolidResponse = await getWellKnownSolid(resourceUrl); // skipIf confuses jest // eslint-disable-next-line jest/no-standalone-expect expect(wellKnownSolidResponse).toMatchSnapshot(); }); - - it("returns the contents of .well-known/solid for the given resource (1.1)", async () => { - mockESS11(); - - const wellKnownSolidResponse = await getWellKnownSolid(resourceUrl); - - expect(wellKnownSolidResponse).toMatchSnapshot(); - }); }); diff --git a/src/resource/solidDataset.ts b/src/resource/solidDataset.ts index 3fa338f05e..48b8a72f67 100644 --- a/src/resource/solidDataset.ts +++ b/src/resource/solidDataset.ts @@ -22,8 +22,7 @@ import type { Quad, NamedNode, Quad_Object, DatasetCore } from "@rdfjs/types"; import { Store as N3Store } from "n3"; import { DataFactory, toRdfJsQuads } from "../rdfjs.internal"; -import { ldp, pim } from "../constants"; -import { getJsonLdParser } from "../formats/jsonLd"; +import { ldp } from "../constants"; import { triplesToTurtle, getTurtleParser } from "../formats/turtle"; import { isLocalNode, isNamedNode, resolveIriForLocalNode } from "../datatypes"; import type { @@ -42,12 +41,10 @@ import { hasResourceInfo, hasChangelog } from "../interfaces"; import { internal_toIriString, normalizeUrl } from "../interfaces.internal"; import { getSourceUrl, - getResourceInfo, isContainer, FetchError, responseToResourceInfo, getContentType, - getLinkedResourceUrlAll, } from "./resource"; import { internal_isUnsuccessfulResponse, @@ -1117,14 +1114,10 @@ function resolveLocalIrisInThing( * please see the [ESS * Documentation](https://docs.inrupt.com/ess/latest/services/discovery-endpoint/#well-known-solid). * - * **Note:** The data contained in this dataset has changed between ESS 1.1 and - * ESS 2.0, as such you will need to check for multiple predicates to support - * both versions. - * * ```typescript * const wellKnown = await getWellKnownSolid(resource); * - * // The wellKnown dataset uses a blank node for the subject all of it’s predicates, + * // The wellKnown dataset uses a blank node for the subject all of its predicates, * // such that we need to call getThingAll with acceptBlankNodes set to true to * // retrieve back predicates contained within the dataset * const wellKnownSubjects = getThingAll(wellKnown, { @@ -1141,9 +1134,6 @@ function resolveLocalIrisInThing( * * * @param url URL of a Resource. - * @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/docs/Web/API/WindowOrWorkerGlobalScope/fetch#parameters). * @returns Promise resolving to a [[SolidDataset]] containing the data at * '.well-known/solid' for the given Resource, or rejecting if fetching it * failed. @@ -1151,7 +1141,6 @@ function resolveLocalIrisInThing( */ export async function getWellKnownSolid( url: UrlString | Url, - options?: Partial<{ fetch?: typeof fetch } & ParseOptions>, ): Promise { const urlString = internal_toIriString(url); @@ -1164,35 +1153,8 @@ export async function getWellKnownSolid( return await getSolidDataset(wellKnownSolidUrl); } catch { - // In case of error, do nothing and try to discover the .well-known - // at the pod's root. - } - - // 1.1s implementation: - const resourceMetadata = await getResourceInfo(urlString, { - fetch: options?.fetch, - // Discovering the .well-known/solid document is useful even for resources - // we don't have access to. - ignoreAuthenticationErrors: true, - }); - const linkedResources = getLinkedResourceUrlAll(resourceMetadata); - const rootResources = linkedResources[pim.storage]; - const rootResource = rootResources?.length === 1 ? rootResources[0] : null; - // If pod root (storage) was advertised, retrieve well known solid from pod's root - if (rootResource !== null) { - const wellKnownSolidUrl = new URL( - ".well-known/solid", - rootResource.endsWith("/") ? rootResource : `${rootResource}/`, - ).href; - return getSolidDataset(wellKnownSolidUrl, { - ...options, - parsers: { - "application/ld+json": getJsonLdParser(), - }, - }); + throw new Error( + "Could not determine storage root or well-known solid resource.", + ); } - - throw new Error( - "Could not determine storage root or well-known solid resource.", - ); }