main
Jackson Morgan 9 months ago
commit 6047c3f1e4
  1. 2
      lerna.json
  2. 1863
      package-lock.json
  3. 9
      packages/cli/package.json
  4. 16
      packages/cli/src/build.ts
  5. 19
      packages/cli/test/.shapes/foafProfile.shex
  6. 2
      packages/dataset/example/loadDataExample.ts
  7. 4
      packages/dataset/package.json
  8. 4
      packages/dataset/test/createExtendedDatasetFromSerializedInput.test.ts
  9. 8
      packages/demo-react/package.json
  10. 6
      packages/jsonld-dataset-proxy/package.json
  11. 1
      packages/jsonld-dataset-proxy/src/util/nodeToJsonldRepresentation.ts
  12. 12
      packages/ldo/package.json
  13. 4
      packages/ldo/src/parseRdf.ts
  14. 2
      packages/rdf-utils/package.json
  15. 2
      packages/rdf-utils/src/serializedToQuads.ts
  16. 4
      packages/schema-converter-shex/package.json
  17. 16
      packages/solid-react/package.json
  18. 10
      packages/solid-react/src/BrowserSolidLdoProvider.tsx
  19. 8
      packages/solid-react/test/test-server/configs/components-config/unauthenticatedServer.json
  20. 5
      packages/solid-react/test/test-server/configs/solid-css-seed.json
  21. 19
      packages/solid-react/test/test-server/solidServer.helper.ts
  22. 1
      packages/solid/babel.config.js
  23. 4
      packages/solid/jest.config.js
  24. 19
      packages/solid/package.json
  25. 8
      packages/solid/src/.ldo/solid.context.ts
  26. 21
      packages/solid/src/.ldo/solid.schema.ts
  27. 13
      packages/solid/src/.ldo/solid.shapeTypes.ts
  28. 16
      packages/solid/src/.ldo/solid.typings.ts
  29. 7
      packages/solid/src/.shapes/solid.shex
  30. 53
      packages/solid/src/SolidLdoDataset.ts
  31. 4
      packages/solid/src/methods.ts
  32. 16
      packages/solid/src/requester/requests/checkRootContainer.ts
  33. 7
      packages/solid/src/requester/requests/readResource.ts
  34. 17
      packages/solid/src/requester/results/error/NoRootContainerError.ts
  35. 8
      packages/solid/src/requester/results/success/CheckRootContainerSuccess.ts
  36. 16
      packages/solid/src/resource/Container.ts
  37. 12
      packages/solid/src/resource/Leaf.ts
  38. 5
      packages/solid/src/resource/Resource.ts
  39. 143
      packages/solid/test/Integration.test.ts
  40. 112
      packages/solid/test/authFetch.helper.ts
  41. 43
      packages/solid/test/configs/server-config.json
  42. 6
      packages/solid/test/configs/solid-css-seed.json
  43. 99
      packages/solid/test/solidServer.helper.ts
  44. 2
      packages/subscribable-dataset/Readme.md
  45. 6
      packages/subscribable-dataset/package.json
  46. 2
      packages/subscribable-dataset/test/createSubscribableDatasetFromSerializedInput.test.ts
  47. 4
      packages/traverser-shexj/package.json
  48. 2
      packages/type-traverser/package.json

@ -1,4 +1,4 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "0.0.1-alpha.23"
"version": "0.0.1-alpha.28"
}

1863
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "@ldo/cli",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.28",
"description": "A Command Line Interface for Linked Data Objects",
"main": "./dist/index.js",
"bin": {
@ -19,7 +19,8 @@
"test": "jest --coverage",
"test:watch": "jest --watch",
"prepublishOnly": "npm run test && npm run build",
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern"
"lint": "eslint src/** --fix --no-error-on-unmatched-pattern",
"build:ldo": "./dist/index.js build --input test/.shapes --output test/.ldo"
},
"repository": {
"type": "git",
@ -43,7 +44,7 @@
"ts-jest": "^27.0.7"
},
"dependencies": {
"@ldo/schema-converter-shex": "^0.0.1-alpha.23",
"@ldo/schema-converter-shex": "^0.0.1-alpha.24",
"@shexjs/parser": "^1.0.0-alpha.24",
"child-process-promise": "^2.2.1",
"commander": "^9.3.0",
@ -59,5 +60,5 @@
"publishConfig": {
"access": "public"
},
"gitHead": "d2364cd2f8da5f0b673b1202d29df5b7c071a17c"
"gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa"
}

@ -38,9 +38,19 @@ export async function build(options: BuildOptions) {
"utf8",
);
// Convert to ShexJ
const schema: Schema = parser
.construct("https://ldo.js.org/")
.parse(shexC);
let schema: Schema;
try {
schema = parser.construct("https://ldo.js.org/").parse(shexC);
} catch (err) {
const errMessage =
err instanceof Error
? err.message
: typeof err === "string"
? err
: "Unknown Error";
console.error(`Error processing ${file.name}: ${errMessage}`);
return;
}
// Convert the content to types
const [typings, context] = await schemaConverterShex(schema);
await Promise.all(

@ -0,0 +1,19 @@
# This shape is provided by default as an example
# You can create your own shape to fit your needs using ShEx (https://shex.io)
# Also check out https://shaperepo.com for examples of more shapes.
PREFIX ex: <https://example.com/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
ex:FoafProfile EXTRA a {
a [ foaf:Person ]
// rdfs:comment "Defines the node as a Person (from foaf)" ;
foaf:name xsd:string ?
// rdfs:comment "Define a person's name." ;
foaf:img xsd:string ?
// rdfs:comment "Photo link but in string form"
foaf:knows @ex:FoafProfile *
// rdfs:comment "A list of WebIds for all the people this user knows." ;
}

@ -34,7 +34,7 @@ async function run(): Promise<void> {
const jsonLdDataset = await serializedToDataset(JSON.stringify(jsonLdData), {
baseIRI:
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#",
format: "application/json-ld",
format: "application/ld+json",
});
// Returns true because the input data describes the same triple.
console.log(turtleDataset.equals(jsonLdDataset));

@ -1,6 +1,6 @@
{
"name": "@ldo/dataset",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.24",
"description": "An RDFJS dataset implementation",
"main": "dist/index.js",
"scripts": {
@ -34,7 +34,7 @@
"ts-node": "^9.1.1"
},
"dependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.23",
"@ldo/rdf-utils": "^0.0.1-alpha.24",
"@rdfjs/dataset": "^1.1.0",
"buffer": "^6.0.3",
"readable-stream": "^4.2.0"

@ -12,7 +12,7 @@ describe("createExtendedDatasetFromSerializedInput", () => {
it.skip("creates a dataset with json-ld", async () => {
const dataset = await serializedToDataset(JSON.stringify(jsonLdData), {
format: "application/json-ld",
format: "application/ld+json",
});
expect(dataset.size).toBe(9);
});
@ -26,7 +26,7 @@ describe("createExtendedDatasetFromSerializedInput", () => {
it.skip("Should error when given invalid JSON", async () => {
await expect(
serializedToDataset('{ bad" json', { format: "application/json-ld" }),
serializedToDataset('{ bad" json', { format: "application/ld+json" }),
).rejects.toThrow("Unexpected token b in JSON at position 2");
});
});

@ -1,9 +1,9 @@
{
"name": "@ldo/demo-react",
"version": "0.0.1-alpha.19",
"version": "0.0.1-alpha.28",
"dependencies": {
"@inrupt/solid-client-authn-browser": "^2.0.0",
"@ldo/solid-react": "^0.0.1-alpha.19",
"@ldo/solid-react": "^0.0.1-alpha.28",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.15.0",
@ -37,13 +37,13 @@
},
"devDependencies": {
"@craco/craco": "^7.1.0",
"@ldo/cli": "^0.0.1-alpha.19",
"@ldo/cli": "^0.0.1-alpha.28",
"@types/jsonld": "^1.5.9",
"@types/react": "^18.2.21",
"@types/shexj": "^2.1.4",
"tsconfig-paths-webpack-plugin": "^4.1.0"
},
"gitHead": "4548985c0de9b0ec83cf5ee93f2d7c1ca2c1b8d8",
"gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa",
"publishConfig": {
"access": "public"
}

@ -1,6 +1,6 @@
{
"name": "@ldo/jsonld-dataset-proxy",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.24",
"description": "",
"main": "dist/index.js",
"scripts": {
@ -40,8 +40,8 @@
"src"
],
"dependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.23",
"@ldo/subscribable-dataset": "^0.0.1-alpha.23",
"@ldo/rdf-utils": "^0.0.1-alpha.24",
"@ldo/subscribable-dataset": "^0.0.1-alpha.24",
"@rdfjs/data-model": "^1.2.0",
"@rdfjs/dataset": "^1.1.0",
"jsonld2graphobject": "^0.0.4"

@ -34,6 +34,7 @@ export function literalToJsonldRepresentation(literal: Literal) {
case "http://www.w3.org/2001/XMLSchema#integer":
case "http://www.w3.org/2001/XMLSchema#byte":
case "http://www.w3.org/2001/XMLSchema#decimal":
case "http://www.w3.org/2001/XMLSchema#double":
case "http://www.w3.org/2001/XMLSchema#int":
case "http://www.w3.org/2001/XMLSchema#long":
case "http://www.w3.org/2001/XMLSchema#negativeInteger":

@ -1,6 +1,6 @@
{
"name": "@ldo/ldo",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.28",
"description": "",
"main": "dist/index.js",
"scripts": {
@ -23,7 +23,7 @@
},
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/ldo#readme",
"devDependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.23",
"@ldo/rdf-utils": "^0.0.1-alpha.24",
"@rdfjs/types": "^1.0.1",
"@types/jest": "^27.0.3",
"@types/jsonld": "^1.5.6",
@ -38,9 +38,9 @@
"typedoc-plugin-markdown": "^3.17.1"
},
"dependencies": {
"@ldo/dataset": "^0.0.1-alpha.23",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.23",
"@ldo/subscribable-dataset": "^0.0.1-alpha.23",
"@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.24",
"@ldo/subscribable-dataset": "^0.0.1-alpha.24",
"@rdfjs/data-model": "^1.2.0",
"buffer": "^6.0.3",
"readable-stream": "^4.3.0"
@ -55,5 +55,5 @@
"publishConfig": {
"access": "public"
},
"gitHead": "d2364cd2f8da5f0b673b1202d29df5b7c071a17c"
"gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa"
}

@ -24,7 +24,7 @@ import type { LdoDataset } from "./LdoDataset";
* import { parseRdf } from "ldo";
*
* const rawTurtle = "...";
* const ldoDataset = parseRdf(rawTurtle, { baseIRI: "https://example.com/" });
* const ldoDataset = await parseRdf(rawTurtle, { baseIRI: "https://example.com/" });
* ```
*/
export async function parseRdf(
@ -47,7 +47,7 @@ export async function parseRdf(
ldoDatasetFactory,
JSON.stringify(data),
{
format: "application/json-ld",
format: "application/ld+json",
},
);
}

@ -1,6 +1,6 @@
{
"name": "@ldo/rdf-utils",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.24",
"description": "Some RDF Utilities to support LDO librariers",
"main": "dist/index.js",
"scripts": {

@ -9,7 +9,7 @@ export async function serializedToQuads(
options?: ParserOptions,
): Promise<Quad[]> {
// JSON-LD Parsing
if (options && options.format === "application/json-ld") {
if (options && options.format === "application/ld+json") {
throw new Error("Not Implemented");
// return new Promise((resolve, reject) => {
// JSON.parse(data);

@ -1,6 +1,6 @@
{
"name": "@ldo/schema-converter-shex",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.24",
"description": "",
"main": "dist/index.js",
"scripts": {
@ -33,7 +33,7 @@
"dist"
],
"dependencies": {
"@ldo/traverser-shexj": "^0.0.1-alpha.23",
"@ldo/traverser-shexj": "^0.0.1-alpha.24",
"dts-dom": "^3.6.0",
"jsonld2graphobject": "^0.0.5"
},

@ -1,6 +1,6 @@
{
"name": "@ldo/solid-react",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.28",
"description": "A React library for LDO and Solid",
"main": "dist/index.js",
"scripts": {
@ -26,7 +26,7 @@
},
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid-react#readme",
"devDependencies": {
"@ldo/rdf-utils": "^0.0.1-alpha.23",
"@ldo/rdf-utils": "^0.0.1-alpha.24",
"@rdfjs/types": "^1.0.1",
"@testing-library/react": "^14.1.2",
"@types/jest": "^27.0.3",
@ -37,11 +37,11 @@
},
"dependencies": {
"@inrupt/solid-client-authn-browser": "^2.0.0",
"@ldo/dataset": "^0.0.1-alpha.23",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.23",
"@ldo/ldo": "^0.0.1-alpha.23",
"@ldo/solid": "^0.0.1-alpha.23",
"@ldo/subscribable-dataset": "^0.0.1-alpha.23",
"@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/jsonld-dataset-proxy": "^0.0.1-alpha.24",
"@ldo/ldo": "^0.0.1-alpha.28",
"@ldo/solid": "^0.0.1-alpha.28",
"@ldo/subscribable-dataset": "^0.0.1-alpha.24",
"@rdfjs/data-model": "^1.2.0",
"cross-fetch": "^3.1.6"
},
@ -52,5 +52,5 @@
"publishConfig": {
"access": "public"
},
"gitHead": "d2364cd2f8da5f0b673b1202d29df5b7c071a17c"
"gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa"
}

@ -27,6 +27,8 @@ export const BrowserSolidLdoProvider: FunctionComponent<PropsWithChildren> = ({
await handleIncomingRedirect({
restorePreviousSession: true,
});
// Set timout to ensure this happens after the redirect
setTimeout(() => {
setSession({ ...getDefaultSession().info });
window.history.replaceState(
{},
@ -36,16 +38,20 @@ export const BrowserSolidLdoProvider: FunctionComponent<PropsWithChildren> = ({
window.localStorage.removeItem(PRE_REDIRECT_URI);
setRanInitialAuthCheck(true);
}, 0);
}, []);
const login = useCallback(async (issuer: string, options?: LoginOptions) => {
const cleanUrl = new URL(window.location.href);
cleanUrl.hash = "";
cleanUrl.search = "";
const fullOptions = {
redirectUrl: window.location.href,
redirectUrl: cleanUrl.toString(),
clientName: "Solid App",
oidcIssuer: issuer,
...options,
};
window.localStorage.setItem(PRE_REDIRECT_URI, fullOptions.redirectUrl);
window.localStorage.setItem(PRE_REDIRECT_URI, window.location.href);
await libraryLogin(fullOptions);
setSession({ ...getDefaultSession().info });
}, []);

@ -1,9 +1,8 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld",
"import": [
"css:config/app/init/initialize-intro.json",
"css:config/app/main/default.json",
"css:config/app/init/initialize-prefilled-root.json",
"css:config/app/setup/optional.json",
"css:config/app/variables/default.json",
"css:config/http/handler/default.json",
"css:config/http/middleware/default.json",
@ -13,9 +12,9 @@
"css:config/identity/access/public.json",
"css:config/identity/email/default.json",
"css:config/identity/handler/default.json",
"css:config/identity/oidc/default.json",
"css:config/identity/ownership/token.json",
"css:config/identity/pod/static.json",
"css:config/identity/registration/enabled.json",
"css:config/ldp/authentication/dpop-bearer.json",
"css:config/ldp/authorization/webacl.json",
"css:config/ldp/handler/default.json",
@ -24,6 +23,7 @@
"css:config/ldp/modes/default.json",
"css:config/storage/backend/memory.json",
"css:config/storage/key-value/resource-store.json",
"css:config/storage/location/root.json",
"css:config/storage/middleware/default.json",
"css:config/util/auxiliary/acl.json",
"css:config/util/identifiers/suffix.json",

@ -1,8 +1,9 @@
[
{
"podName": "example",
"email": "hello@example.com",
"password": "abc123",
"template": "./template"
"pods": [
{ "name": "example" }
]
}
]

@ -12,28 +12,25 @@ export async function createApp(): Promise<App> {
} as App;
}
const appRunner = new AppRunner();
return appRunner.create(
{
return appRunner.create({
loaderProperties: {
mainModulePath: resolveModulePath(""),
typeChecking: false,
},
path.join(
config: path.join(
__dirname,
"configs",
"components-config",
"unauthenticatedServer.json",
),
{},
{
variableBindings: {},
shorthand: {
port: 3_001,
loggingLevel: "off",
seededPodConfigJson: path.join(
__dirname,
"configs",
"solid-css-seed.json",
),
seedConfig: path.join(__dirname, "configs", "solid-css-seed.json"),
},
);
});
}
export interface ISecretData {

@ -0,0 +1 @@
module.exports = { presets: ["@babel/preset-env"] };

@ -4,4 +4,8 @@ module.exports = {
...sharedConfig,
rootDir: "./",
setupFiles: ["<rootDir>/test/setup-tests.ts"],
transform: {
"^.+\\.(ts|tsx)?$": "ts-jest",
"^.+\\.(js|jsx)$": "babel-jest",
},
};

@ -1,13 +1,13 @@
{
"name": "@ldo/solid",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.28",
"description": "A library for LDO and Solid",
"main": "dist/index.js",
"scripts": {
"example": "ts-node ./example/example.ts",
"build": "tsc --project tsconfig.build.json",
"watch": "tsc --watch",
"test": "jest --coverage",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
"test:watch": "jest --watch",
"prepublishOnly": "npm run test && npm run build",
"build:ldo": "ldo build --input src/.shapes --output src/.ldo",
@ -25,12 +25,13 @@
},
"homepage": "https://github.com/o-development/ldobjects/tree/main/packages/solid#readme",
"devDependencies": {
"@inrupt/solid-client-authn-core": "^1.17.1",
"@ldo/cli": "^0.0.1-alpha.23",
"@inrupt/solid-client-authn-core": "^2.2.6",
"@ldo/cli": "^0.0.1-alpha.28",
"@rdfjs/data-model": "^1.2.0",
"@rdfjs/types": "^1.0.1",
"@solid/community-server": "^6.0.2",
"@solid/community-server": "^7.1.3",
"@types/jest": "^27.0.3",
"cross-env": "^7.0.3",
"dotenv": "^16.3.1",
"jest-rdf": "^1.8.0",
"ts-jest": "^27.1.2",
@ -40,9 +41,9 @@
"typedoc-plugin-markdown": "^3.17.1"
},
"dependencies": {
"@ldo/dataset": "^0.0.1-alpha.23",
"@ldo/ldo": "^0.0.1-alpha.23",
"@ldo/rdf-utils": "^0.0.1-alpha.23",
"@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/ldo": "^0.0.1-alpha.28",
"@ldo/rdf-utils": "^0.0.1-alpha.24",
"cross-fetch": "^3.1.6",
"http-link-header": "^1.1.1"
},
@ -53,5 +54,5 @@
"publishConfig": {
"access": "public"
},
"gitHead": "d2364cd2f8da5f0b673b1202d29df5b7c071a17c"
"gitHead": "c63f055aab22155b60a5fdee4172979b9c287dfa"
}

@ -1,4 +1,4 @@
import type { ContextDefinition } from "jsonld";
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
@ -15,7 +15,6 @@ export const solidContext: ContextDefinition = {
modified: {
"@id": "http://purl.org/dc/terms/modified",
"@type": "http://www.w3.org/2001/XMLSchema#string",
"@container": "@set",
},
contains: {
"@id": "http://www.w3.org/ns/ldp#contains",
@ -26,11 +25,14 @@ export const solidContext: ContextDefinition = {
mtime: {
"@id": "http://www.w3.org/ns/posix/stat#mtime",
"@type": "http://www.w3.org/2001/XMLSchema#decimal",
"@container": "@set",
},
size: {
"@id": "http://www.w3.org/ns/posix/stat#size",
"@type": "http://www.w3.org/2001/XMLSchema#integer",
},
storage: {
"@id": "http://www.w3.org/ns/pim/space#storage",
"@type": "@id",
"@container": "@set",
},
};

@ -1,4 +1,4 @@
import type { Schema } from "shexj";
import { Schema } from "shexj";
/**
* =============================================================================
@ -210,5 +210,24 @@ export const solidSchema: Schema = {
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
{
id: "http://www.w3.org/ns/lddps#ProfileWithStorage",
type: "ShapeDecl",
shapeExpr: {
type: "Shape",
expression: {
id: "http://www.w3.org/ns/lddps#ProfileWithStorageShape",
type: "TripleConstraint",
predicate: "http://www.w3.org/ns/pim/space#storage",
valueExpr: {
type: "NodeConstraint",
nodeKind: "iri",
},
min: 0,
max: -1,
},
extra: ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"],
},
},
],
};

@ -1,7 +1,7 @@
import type { ShapeType } from "@ldo/ldo";
import { ShapeType } from "@ldo/ldo";
import { solidSchema } from "./solid.schema";
import { solidContext } from "./solid.context";
import type { Container, Resource } from "./solid.typings";
import { Container, Resource, ProfileWithStorage } from "./solid.typings";
/**
* =============================================================================
@ -26,3 +26,12 @@ export const ResourceShapeType: ShapeType<Resource> = {
shape: "http://www.w3.org/ns/lddps#Resource",
context: solidContext,
};
/**
* ProfileWithStorage ShapeType
*/
export const ProfileWithStorageShapeType: ShapeType<ProfileWithStorage> = {
schema: solidSchema,
shape: "http://www.w3.org/ns/lddps#ProfileWithStorage",
context: solidContext,
};

@ -1,4 +1,4 @@
import type { ContextDefinition } from "jsonld";
import { ContextDefinition } from "jsonld";
/**
* =============================================================================
@ -57,9 +57,6 @@ export interface Resource {
| {
"@id": "Resource2";
}
| {
"@id": "Container";
}
)[];
/**
* Date modified
@ -74,3 +71,14 @@ export interface Resource {
*/
size?: number;
}
/**
* ProfileWithStorage Type
*/
export interface ProfileWithStorage {
"@id"?: string;
"@context"?: ContextDefinition;
storage?: {
"@id": string;
}[];
}

@ -6,6 +6,7 @@ PREFIX ldps: <http://www.w3.org/ns/lddps#>
PREFIX dct: <http://purl.org/dc/terms/>
PREFIX stat: <http://www.w3.org/ns/posix/stat#>
PREFIX tur: <http://www.w3.org/ns/iana/media-types/text/turtle#>
PREFIX pim: <http://www.w3.org/ns/pim/space#>
ldps:Container EXTRA a {
$ldps:ContainerShape (
@ -34,3 +35,9 @@ ldps:Resource EXTRA a {
// rdfs:comment "size of this container";
)
}
ldps:ProfileWithStorage EXTRA a {
$ldps:ProfileWithStorageShape (
pim:storage IRI *;
)
}

@ -10,6 +10,11 @@ import { SolidLdoTransactionDataset } from "./SolidLdoTransactionDataset";
import type { ITransactionDatasetFactory } from "@ldo/subscribable-dataset";
import type { SubjectNode } from "@ldo/rdf-utils";
import type { Resource } from "./resource/Resource";
import type { CheckRootResultError } from "./requester/requests/checkRootContainer";
import type { NoRootContainerError } from "./requester/results/error/NoRootContainerError";
import type { ReadResultError } from "./requester/requests/readResource";
import { ProfileWithStorageShapeType } from "./.ldo/solid.shapeTypes";
import type { GetStorageContainerFromWebIdSuccess } from "./requester/results/success/CheckRootContainerSuccess";
/**
* A SolidLdoDataset has all the functionality of an LdoDataset with the added
@ -113,4 +118,52 @@ export class SolidLdoDataset extends LdoDataset {
startTransaction(linkedDataObject);
return linkedDataObject;
}
/**
* Gets a list of root storage containers for a user given their WebId
* @param webId: The webId for the user
* @returns A list of storages if successful, an error if not
* @example
* ```typescript
* const result = await solidLdoDataset
* .getStorageFromWebId("https://example.com/profile/card#me");
* if (result.isError) {
* // Do something
* }
* console.log(result.storageContainer[0].uri);
* ```
*/
async getStorageFromWebId(
webId: LeafUri,
): Promise<
| GetStorageContainerFromWebIdSuccess
| CheckRootResultError
| ReadResultError
| NoRootContainerError
> {
const webIdResource = this.getResource(webId);
const readResult = await webIdResource.readIfUnfetched();
if (readResult.isError) return readResult;
const profile = this.usingType(ProfileWithStorageShapeType).fromSubject(
webId,
);
if (profile.storage && profile.storage.length > 0) {
const containers = profile.storage.map((storageNode) =>
this.getResource(storageNode["@id"] as ContainerUri),
);
return {
type: "getStorageContainerFromWebIdSuccess",
isError: false,
storageContainers: containers,
};
}
const getContainerResult = await webIdResource.getRootContainer();
if (getContainerResult.type === "container")
return {
type: "getStorageContainerFromWebIdSuccess",
isError: false,
storageContainers: [getContainerResult],
};
return getContainerResult;
}
}

@ -22,7 +22,7 @@ import type { SolidLdoTransactionDataset } from "./SolidLdoTransactionDataset";
*
* const profile = solidLdoDataset
* .using(ProfileShapeType)
* .fromSubject("https://example.com/proifle#me");
* .fromSubject("https://example.com/profile#me");
* const resource = solidLdoDataset.getResource("https://example.com/profile");
*
* const cProfile = changeData(profile, resource);
@ -60,7 +60,7 @@ export function changeData<Type extends LdoBase>(
*
* const profile = solidLdoDataset
* .using(ProfileShapeType)
* .fromSubject("https://example.com/proifle#me");
* .fromSubject("https://example.com/profile#me");
* const resource = solidLdoDataset.getResource("https://example.com/profile");
*
* const cProfile = changeData(profile, resource);

@ -1,6 +1,5 @@
import type { BasicRequestOptions } from "./requestOptions";
import { parse as parseLinkHeader } from "http-link-header";
import { NoncompliantPodError } from "../results/error/NoncompliantPodError";
import type { CheckRootContainerSuccess } from "../results/success/CheckRootContainerSuccess";
import type {
HttpErrorResultType,
@ -21,7 +20,6 @@ export type CheckRootResult = CheckRootContainerSuccess | CheckRootResultError;
*/
export type CheckRootResultError =
| HttpErrorResultType
| NoncompliantPodError
| UnexpectedHttpError
| UnexpectedResourceError;
@ -37,10 +35,15 @@ export type CheckRootResultError =
export function checkHeadersForRootContainer(
uri: ContainerUri,
headers: Headers,
): CheckRootContainerSuccess | NoncompliantPodError {
): CheckRootContainerSuccess {
const linkHeader = headers.get("link");
if (!linkHeader) {
return new NoncompliantPodError(uri, "No link header present in request.");
return {
uri,
isRootContainer: false,
type: "checkRootContainerSuccess",
isError: false,
};
}
const parsedLinkHeader = parseLinkHeader(linkHeader);
const types = parsedLinkHeader.get("rel", "type");
@ -82,7 +85,10 @@ export async function checkRootContainer(
try {
const fetch = guaranteeFetch(options?.fetch);
// Fetch options to determine the document type
const response = await fetch(uri, { method: "HEAD" });
// Note cache: "no-store": we don't want to depend on cached results because
// web browsers do not cache link headers
// https://github.com/CommunitySolidServer/CommunitySolidServer/issues/1959
const response = await fetch(uri, { method: "HEAD", cache: "no-store" });
const httpErrorResult = HttpErrorResult.checkResponse(uri, response);
if (httpErrorResult) return httpErrorResult;

@ -99,7 +99,9 @@ export async function readResource(
try {
const fetch = guaranteeFetch(options?.fetch);
// Fetch options to determine the document type
const response = await fetch(uri);
const response = await fetch(uri, {
headers: { accept: "text/turtle, */*" },
});
if (response.status === 404) {
return {
isError: false,
@ -124,7 +126,7 @@ export async function readResource(
);
}
if (contentType === "text/turtle") {
if (contentType.startsWith("text/turtle")) {
// Parse Turtle
const rawTurtle = await response.text();
if (options?.dataset) {
@ -137,7 +139,6 @@ export async function readResource(
}
if (isContainerUri(uri)) {
const result = checkHeadersForRootContainer(uri, response.headers);
if (result.isError) return result;
return {
isError: false,
type: "containerReadSuccess",

@ -0,0 +1,17 @@
import { ResourceError } from "./ErrorResult";
/**
* A NoncompliantPodError is returned when the server responded in a way that is
* not compliant with the Solid specification.
*/
export class NoRootContainerError extends ResourceError {
readonly type = "noRootContainerError" as const;
/**
* @param uri - the URI of the requested resource
* @param message - a custom message for the error
*/
constructor(uri: string) {
super(uri, `${uri} has not root container.`);
}
}

@ -1,4 +1,5 @@
import type { ResourceSuccess } from "./SuccessResult";
import type { Container } from "../../../resource/Container";
import type { ResourceSuccess, SuccessResult } from "./SuccessResult";
/**
* Indicates that the request to check if a resource is the root container was
@ -11,3 +12,8 @@ export interface CheckRootContainerSuccess extends ResourceSuccess {
*/
isRootContainer: boolean;
}
export interface GetStorageContainerFromWebIdSuccess extends SuccessResult {
type: "getStorageContainerFromWebIdSuccess";
storageContainers: Container[];
}

@ -19,7 +19,6 @@ import type {
ReadResultError,
} from "../requester/requests/readResource";
import { AggregateError } from "../requester/results/error/ErrorResult";
import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError";
import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess";
import type { AbsentReadSuccess } from "../requester/results/success/ReadSuccess";
import type { ContainerReadSuccess } from "../requester/results/success/ReadSuccess";
@ -31,6 +30,7 @@ import type { Leaf } from "./Leaf";
import type { SharedStatuses } from "./Resource";
import { Resource } from "./Resource";
import type { ResourceResult } from "./resourceResult/ResourceResult";
import { NoRootContainerError } from "../requester/results/error/NoRootContainerError";
/**
* Represents the current status of a specific container on a Pod as known by
@ -222,7 +222,8 @@ export class Container extends Resource {
/**
* Gets the root container of this container. If this container is the root
* container, this function returns itself.
* @returns The root container for this container
* @returns The root container for this container or undefined if there is no
* root container.
*
* @example
* Suppose the root container is at `https://example.com/`
@ -237,11 +238,13 @@ export class Container extends Resource {
* }
* ```
*/
async getRootContainer(): Promise<Container | CheckRootResultError> {
async getRootContainer(): Promise<
Container | CheckRootResultError | NoRootContainerError
> {
const parentContainerResult = await this.getParentContainer();
if (parentContainerResult?.isError) return parentContainerResult;
if (!parentContainerResult) {
return this;
return this.isRootContainer() ? this : new NoRootContainerError(this.uri);
}
return parentContainerResult.getRootContainer();
}
@ -277,10 +280,7 @@ export class Container extends Resource {
if (this.rootContainer) return undefined;
const parentUri = getParentUri(this.uri);
if (!parentUri) {
return new NoncompliantPodError(
this.uri,
`${this.uri} is not root does not have a parent container`,
);
return undefined;
}
return this.context.resourceStore.get(parentUri);
}

@ -23,6 +23,7 @@ import type { Container } from "./Container";
import type { SharedStatuses } from "./Resource";
import { Resource } from "./Resource";
import type { ResourceResult } from "./resourceResult/ResourceResult";
import type { NoRootContainerError } from "../requester/results/error/NoRootContainerError";
/**
* Represents the current status of a specific Leaf on a Pod as known by LDO.
@ -77,7 +78,9 @@ export class Leaf extends Resource {
*/
constructor(uri: LeafUri, context: SolidLdoDatasetContext) {
super(context);
this.uri = uri;
const uriObject = new URL(uri);
uriObject.hash = "";
this.uri = uriObject.toString() as LeafUri;
this.requester = new LeafBatchedRequester(uri, context);
this.status = { isError: false, type: "unfetched", uri };
}
@ -338,7 +341,12 @@ export class Leaf extends Resource {
* }
* ```
*/
async getRootContainer(): Promise<Container | CheckRootResultError> {
async getRootContainer(): Promise<
Container | CheckRootResultError | NoRootContainerError
> {
// Check to see if this document has a pim:storage if so, use that
// If not, traverse the tree
const parent = await this.getParentContainer();
return parent.getRootContainer();
}

@ -32,6 +32,7 @@ import { getWacRuleWithAclUri, type GetWacRuleResult } from "./wac/getWacRule";
import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError";
import { setWacRuleForAclUri, type SetWacRuleResult } from "./wac/setWacRule";
import type { LeafUri } from "../util/uriTypes";
import type { NoRootContainerError } from "../requester/results/error/NoRootContainerError";
/**
* Statuses shared between both Leaf and Container
@ -524,7 +525,9 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{
* }
* ```
*/
abstract getRootContainer(): Promise<Container | CheckRootResultError>;
abstract getRootContainer(): Promise<
Container | CheckRootResultError | NoRootContainerError
>;
abstract getParentContainer(): Promise<
Container | CheckRootResultError | undefined

@ -8,12 +8,7 @@ import type {
UpdateResultError,
} from "../src";
import { changeData, commitData, createSolidLdoDataset } from "../src";
import {
ROOT_CONTAINER,
WEB_ID,
createApp,
getAuthenticatedFetch,
} from "./solidServer.helper";
import { ROOT_CONTAINER, WEB_ID, createApp } from "./solidServer.helper";
import {
namedNode,
quad as createQuad,
@ -45,6 +40,8 @@ import type {
import type { NoncompliantPodError } from "../src/requester/results/error/NoncompliantPodError";
import type { GetWacRuleSuccess } from "../src/resource/wac/results/GetWacRuleSuccess";
import type { WacRule } from "../src/resource/wac/WacRule";
import type { GetStorageContainerFromWebIdSuccess } from "../src/requester/results/success/CheckRootContainerSuccess";
import { generateAuthFetch } from "./authFetch.helper";
const TEST_CONTAINER_SLUG = "test_ldo/";
const TEST_CONTAINER_URI =
@ -58,6 +55,7 @@ const SAMPLE2_BINARY_URI =
`${TEST_CONTAINER_URI}${SAMPLE2_BINARY_SLUG}` as LeafUri;
const SAMPLE_CONTAINER_URI =
`${TEST_CONTAINER_URI}sample_container/` as ContainerUri;
const SAMPLE_PROFILE_URI = `${TEST_CONTAINER_URI}profile.ttl` as LeafUri;
const SPIDER_MAN_TTL = `@base <http://example.org/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@ -98,6 +96,11 @@ const TEST_CONTAINER_ACL = `<#b30e3fd1-b5a8-4763-ad9d-e95de9cf7933> a <http://ww
<http://www.w3.org/ns/auth/acl#mode> <http://www.w3.org/ns/auth/acl#Read>, <http://www.w3.org/ns/auth/acl#Write>, <http://www.w3.org/ns/auth/acl#Append>, <http://www.w3.org/ns/auth/acl#Control>;
<http://www.w3.org/ns/auth/acl#agent> <${WEB_ID}>;
<http://www.w3.org/ns/auth/acl#agentClass> <http://xmlns.com/foaf/0.1/Agent>, <http://www.w3.org/ns/auth/acl#AuthenticatedAgent>.`;
const SAMPLE_PROFILE_TTL = `
@prefix pim: <http://www.w3.org/ns/pim/space#> .
<${SAMPLE_PROFILE_URI}> pim:storage <https://example.com/A/>, <https://example.com/B/> .
`;
async function testRequestLoads<ReturnVal>(
request: () => Promise<ReturnVal>,
@ -150,16 +153,25 @@ describe("Integration", () => {
>;
let solidLdoDataset: SolidLdoDataset;
let previousJestId: string | undefined;
let previousNodeEnv: string | undefined;
beforeAll(async () => {
// Remove Jest ID so that community solid server doesn't use the Jest Import
previousJestId = process.env.JEST_WORKER_ID;
previousNodeEnv = process.env.NODE_ENV;
delete process.env.JEST_WORKER_ID;
process.env.NODE_ENV = "other_test";
// Start up the server
app = await createApp();
await app.start();
authFetch = await getAuthenticatedFetch();
authFetch = await generateAuthFetch();
});
afterAll(async () => {
app.stop();
process.env.JEST_WORKER_ID = previousJestId;
process.env.NODE_ENV = previousNodeEnv;
});
beforeEach(async () => {
@ -191,6 +203,11 @@ describe("Integration", () => {
headers: { "content-type": "text/plain", slug: "sample.txt" },
body: "some text.",
}),
authFetch(TEST_CONTAINER_URI, {
method: "POST",
headers: { "content-type": "text/turtle", slug: "profile.ttl" },
body: SAMPLE_PROFILE_TTL,
}),
]);
});
@ -208,6 +225,9 @@ describe("Integration", () => {
authFetch(SAMPLE2_BINARY_URI, {
method: "DELETE",
}),
authFetch(SAMPLE_PROFILE_URI, {
method: "DELETE",
}),
authFetch(SAMPLE_CONTAINER_URI, {
method: "DELETE",
}),
@ -217,6 +237,18 @@ describe("Integration", () => {
});
});
/**
* General
*/
describe("General", () => {
it("Does not include the hash when creating a resource", () => {
const resource = solidLdoDataset.getResource(
"https://example.com/thing#hash",
);
expect(resource.uri).toBe("https://example.com/thing");
});
});
/**
* Read
*/
@ -271,7 +303,7 @@ describe("Integration", () => {
isDoingInitialFetch: true,
});
expect(result.type).toBe("containerReadSuccess");
expect(resource.children().length).toBe(2);
expect(resource.children().length).toBe(3);
});
it("Reads a binary leaf", async () => {
@ -385,6 +417,24 @@ describe("Integration", () => {
);
});
it("Parses Turtle even when the content type contains parameters", async () => {
fetchMock.mockResolvedValueOnce(
new Response(SPIDER_MAN_TTL, {
status: 200,
headers: new Headers({ "content-type": "text/turtle;charset=utf-8" }),
}),
);
const resource = solidLdoDataset.getResource(SAMPLE_DATA_URI);
const result = await testRequestLoads(() => resource.read(), resource, {
isLoading: true,
isReading: true,
isDoingInitialFetch: true,
});
expect(result.isError).toBe(false);
if (result.isError) return;
expect(result.type).toBe("dataReadSuccess");
});
it("Returns an UnexpectedResourceError if an unknown error is triggered", async () => {
fetchMock.mockRejectedValueOnce(new Error("Something happened."));
const resource = solidLdoDataset.getResource(SAMPLE2_DATA_URI);
@ -399,7 +449,7 @@ describe("Integration", () => {
expect(result.message).toBe("Something happened.");
});
it("Returns an error if there is no link header for a container request", async () => {
it("Does not return an error if there is no link header for a container request", async () => {
fetchMock.mockResolvedValueOnce(
new Response(TEST_CONTAINER_TTL, {
status: 200,
@ -412,12 +462,9 @@ describe("Integration", () => {
isReading: true,
isDoingInitialFetch: true,
});
expect(result.isError).toBe(true);
if (!result.isError) return;
expect(result.type).toBe("noncompliantPodError");
expect(result.message).toMatch(
/\Response from .* is not compliant with the Solid Specification: No link header present in request\./,
);
expect(result.isError).toBe(false);
if (result.isError) return;
expect(result.resource.isRootContainer()).toBe(false);
});
it("knows nothing about a leaf resource if it is not fetched", () => {
@ -470,7 +517,7 @@ describe("Integration", () => {
},
);
expect(result.type).toBe("containerReadSuccess");
expect(resource.children().length).toBe(2);
expect(resource.children().length).toBe(3);
});
it("reads an unfetched leaf", async () => {
@ -501,7 +548,7 @@ describe("Integration", () => {
const result = await resource.readIfUnfetched();
expect(fetchMock).not.toHaveBeenCalled();
expect(result.type).toBe("containerReadSuccess");
expect(resource.children().length).toBe(2);
expect(resource.children().length).toBe(3);
});
it("returns a cached existing data leaf", async () => {
@ -559,7 +606,19 @@ describe("Integration", () => {
expect(result.isRootContainer()).toBe(true);
});
it("Returns an error if there is no link header for a container request", async () => {
it("Returns an error if there is no root container", async () => {
fetchMock.mockResolvedValueOnce(
new Response(TEST_CONTAINER_TTL, {
status: 200,
headers: new Headers({ "content-type": "text/turtle" }),
}),
);
fetchMock.mockResolvedValueOnce(
new Response(TEST_CONTAINER_TTL, {
status: 200,
headers: new Headers({ "content-type": "text/turtle" }),
}),
);
fetchMock.mockResolvedValueOnce(
new Response(TEST_CONTAINER_TTL, {
status: 200,
@ -570,10 +629,8 @@ describe("Integration", () => {
const result = await resource.getRootContainer();
expect(result.isError).toBe(true);
if (!result.isError) return;
expect(result.type).toBe("noncompliantPodError");
expect(result.message).toMatch(
/\Response from .* is not compliant with the Solid Specification: No link header present in request\./,
);
expect(result.type).toBe("noRootContainerError");
expect(result.message).toMatch(/\.* has not root container\./);
});
it("An error to be returned if a common http error is encountered", async () => {
@ -611,7 +668,47 @@ describe("Integration", () => {
const resource = solidLdoDataset.getResource(ROOT_CONTAINER);
const result = await resource.getRootContainer();
expect(result.isError).toBe(true);
expect(result.type).toBe("noncompliantPodError");
expect(result.type).toBe("noRootContainerError");
});
});
/**
* Get Storage From WebId
*/
describe("getStorageFromWebId", () => {
it("Gets storage when a pim:storage field isn't present", async () => {
const result = await solidLdoDataset.getStorageFromWebId(SAMPLE_DATA_URI);
expect(result.type).toBe("getStorageContainerFromWebIdSuccess");
const realResult = result as GetStorageContainerFromWebIdSuccess;
expect(realResult.storageContainers.length).toBe(1);
expect(realResult.storageContainers[0].uri).toBe(ROOT_CONTAINER);
});
it("Gets storage when a pim:storage field is present", async () => {
const result =
await solidLdoDataset.getStorageFromWebId(SAMPLE_PROFILE_URI);
expect(result.type).toBe("getStorageContainerFromWebIdSuccess");
const realResult = result as GetStorageContainerFromWebIdSuccess;
expect(realResult.storageContainers.length).toBe(2);
expect(realResult.storageContainers[0].uri).toBe(
"https://example.com/A/",
);
expect(realResult.storageContainers[1].uri).toBe(
"https://example.com/B/",
);
});
it("Passes any errors returned from the read method", async () => {
fetchMock.mockRejectedValueOnce(new Error("Something happened."));
const result = await solidLdoDataset.getStorageFromWebId(SAMPLE_DATA_URI);
expect(result.isError).toBe(true);
});
it("Passes any errors returned from the getRootContainer method", async () => {
fetchMock.mockResolvedValueOnce(new Response(""));
fetchMock.mockRejectedValueOnce(new Error("Something happened."));
const result = await solidLdoDataset.getStorageFromWebId(SAMPLE_DATA_URI);
expect(result.isError).toBe(true);
});
});

@ -0,0 +1,112 @@
import type { KeyPair } from "@inrupt/solid-client-authn-core";
import {
buildAuthenticatedFetch,
createDpopHeader,
generateDpopKeyPair,
} from "@inrupt/solid-client-authn-core";
import fetch from "cross-fetch";
const config = {
podName: process.env.USER_NAME || "example",
email: process.env.EMAIL || "hello@example.com",
password: process.env.PASSWORD || "abc123",
};
async function getAuthorization(): Promise<string> {
// First we request the account API controls to find out where we can log in
const indexResponse = await fetch("http://localhost:3001/.account/");
const { controls } = await indexResponse.json();
// And then we log in to the account API
const response = await fetch(controls.password.login, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
email: config.email,
password: config.password,
}),
});
// This authorization value will be used to authenticate in the next step
const result = await response.json();
return result.authorization;
}
async function getSecret(
authorization: string,
): Promise<{ id: string; secret: string; resource: string }> {
// Now that we are logged in, we need to request the updated controls from the server.
// These will now have more values than in the previous example.
const indexResponse = await fetch("http://localhost:3001/.account/", {
headers: { authorization: `CSS-Account-Token ${authorization}` },
});
const { controls } = await indexResponse.json();
// Here we request the server to generate a token on our account
const response = await fetch(controls.account.clientCredentials, {
method: "POST",
headers: {
authorization: `CSS-Account-Token ${authorization}`,
"content-type": "application/json",
},
// The name field will be used when generating the ID of your token.
// The WebID field determines which WebID you will identify as when using the token.
// Only WebIDs linked to your account can be used.
body: JSON.stringify({
name: "my-token",
webId: `http://localhost:3001/${config.podName}/profile/card#me`,
}),
});
// These are the identifier and secret of your token.
// Store the secret somewhere safe as there is no way to request it again from the server!
// The `resource` value can be used to delete the token at a later point in time.
const response2 = await response.json();
return response2;
}
async function getAccessToken(
id: string,
secret: string,
): Promise<{ accessToken: string; dpopKey: KeyPair }> {
try {
// A key pair is needed for encryption.
// This function from `solid-client-authn` generates such a pair for you.
const dpopKey = await generateDpopKeyPair();
// These are the ID and secret generated in the previous step.
// Both the ID and the secret need to be form-encoded.
const authString = `${encodeURIComponent(id)}:${encodeURIComponent(
secret,
)}`;
// This URL can be found by looking at the "token_endpoint" field at
// http://localhost:3001/.well-known/openid-configuration
// if your server is hosted at http://localhost:3000/.
const tokenUrl = "http://localhost:3001/.oidc/token";
const response = await fetch(tokenUrl, {
method: "POST",
headers: {
// The header needs to be in base64 encoding.
authorization: `Basic ${Buffer.from(authString).toString("base64")}`,
"content-type": "application/x-www-form-urlencoded",
dpop: await createDpopHeader(tokenUrl, "POST", dpopKey),
},
body: "grant_type=client_credentials&scope=webid",
});
// This is the Access token that will be used to do an authenticated request to the server.
// The JSON also contains an "expires_in" field in seconds,
// which you can use to know when you need request a new Access token.
const response2 = await response.json();
return { accessToken: response2.access_token, dpopKey };
} catch (err) {
console.error(err);
throw err;
}
}
export async function generateAuthFetch() {
const authorization = await getAuthorization();
const { id, secret } = await getSecret(authorization);
const { accessToken, dpopKey } = await getAccessToken(id, secret);
return await buildAuthenticatedFetch(accessToken, { dpopKey });
}

@ -0,0 +1,43 @@
{
"@context": [
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld"
],
"import": [
"css:config/app/init/static-root.json",
"css:config/app/main/default.json",
"css:config/app/variables/default.json",
"css:config/http/handler/default.json",
"css:config/http/middleware/default.json",
"css:config/http/notifications/all.json",
"css:config/http/server-factory/http.json",
"css:config/http/static/default.json",
"css:config/identity/access/public.json",
"css:config/identity/email/default.json",
"css:config/identity/handler/default.json",
"css:config/identity/oidc/default.json",
"css:config/identity/ownership/token.json",
"css:config/identity/pod/static.json",
"css:config/ldp/authentication/dpop-bearer.json",
"css:config/ldp/authorization/webacl.json",
"css:config/ldp/handler/default.json",
"css:config/ldp/metadata-parser/default.json",
"css:config/ldp/metadata-writer/default.json",
"css:config/ldp/modes/default.json",
"css:config/storage/backend/memory.json",
"css:config/storage/key-value/resource-store.json",
"css:config/storage/location/pod.json",
"css:config/storage/middleware/default.json",
"css:config/util/auxiliary/acl.json",
"css:config/util/identifiers/suffix.json",
"css:config/util/index/default.json",
"css:config/util/logging/winston.json",
"css:config/util/representation-conversion/default.json",
"css:config/util/resource-locker/memory.json",
"css:config/util/variables/default.json"
],
"@graph": [
{
"comment": "A Solid server that stores its resources on disk and uses WAC for authorization."
}
]
}

@ -1,7 +1,9 @@
[
{
"podName": "example",
"email": "hello@example.com",
"password": "abc123"
"password": "abc123",
"pods": [
{ "name": "example" }
]
}
]

@ -1,24 +1,9 @@
// Taken from https://github.com/comunica/comunica/blob/b237be4265c353a62a876187d9e21e3bc05123a3/engines/query-sparql/test/QuerySparql-solid-test.ts#L9
import * as path from "path";
import type { KeyPair } from "@inrupt/solid-client-authn-core";
import {
buildAuthenticatedFetch,
createDpopHeader,
generateDpopKeyPair,
} from "@inrupt/solid-client-authn-core";
import type { App } from "@solid/community-server";
import { AppRunner, resolveModulePath } from "@solid/community-server";
import "jest-rdf";
import fetch from "cross-fetch";
const config = [
{
podName: process.env.USER_NAME || "example",
email: process.env.EMAIL || "hello@example.com",
password: process.env.PASSWORD || "abc123",
},
];
export const SERVER_DOMAIN = process.env.SERVER || "http://localhost:3001/";
export const ROOT_ROUTE = process.env.ROOT_CONTAINER || "";
@ -37,88 +22,18 @@ export async function createApp(): Promise<App> {
} as App;
}
const appRunner = new AppRunner();
return appRunner.create(
{
return appRunner.create({
loaderProperties: {
mainModulePath: resolveModulePath(""),
typeChecking: false,
},
resolveModulePath("config/default.json"),
{},
{
config: resolveModulePath("config/default.json"),
variableBindings: {},
shorthand: {
port: 3_001,
loggingLevel: "off",
seededPodConfigJson: path.join(
__dirname,
"configs",
"solid-css-seed.json",
),
},
);
}
export interface ISecretData {
id: string;
secret: string;
}
// From https://communitysolidserver.github.io/CommunitySolidServer/5.x/usage/client-credentials/
export async function getSecret(): Promise<ISecretData> {
const result = await fetch(`${SERVER_DOMAIN}idp/credentials/`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
email: config[0].email,
password: config[0].password,
name: config[0].podName,
}),
});
const json = await result.json();
return json;
}
export interface ITokenData {
accessToken: string;
dpopKey: KeyPair;
}
// From https://communitysolidserver.github.io/CommunitySolidServer/5.x/usage/client-credentials/
export async function refreshToken({
id,
secret,
}: ISecretData): Promise<ITokenData> {
const dpopKey = await generateDpopKeyPair();
const authString = `${encodeURIComponent(id)}:${encodeURIComponent(secret)}`;
const tokenUrl = `${SERVER_DOMAIN}.oidc/token`;
const accessToken = await fetch(tokenUrl, {
method: "POST",
headers: {
// The header needs to be in base64 encoding.
authorization: `Basic ${Buffer.from(authString).toString("base64")}`,
"content-type": "application/x-www-form-urlencoded",
dpop: await createDpopHeader(tokenUrl, "POST", dpopKey),
seedConfig: path.join(__dirname, "configs", "solid-css-seed.json"),
},
body: "grant_type=client_credentials&scope=webid",
})
.then((res) => res.json())
.then((res) => res.access_token);
return { accessToken, dpopKey };
}
export async function getAuthenticatedFetch() {
// Generate secret
const secret = await getSecret();
if (!secret) throw new Error("No Secret");
// Get token
const token = await refreshToken(secret);
if (!token) throw new Error("No Token");
// Build authenticated fetch
const authFetch = await buildAuthenticatedFetch(fetch, token.accessToken, {
dpopKey: token.dpopKey,
});
return authFetch;
}

@ -86,7 +86,7 @@ async function run(): Promise<void> {
{
baseIRI:
"https://jackson.solidcommunity.net/IndividualChats/jackson.solidcommunity.net/index.ttl#",
format: "application/json-ld",
format: "application/ld+json",
}
);
// Returns true because the input data describes the same triple.

@ -1,6 +1,6 @@
{
"name": "@ldo/subscribable-dataset",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.24",
"description": "An RDFJS dataset implementation that can be subscribed to for updates",
"main": "dist/index.js",
"scripts": {
@ -33,8 +33,8 @@
"ts-node": "^9.1.1"
},
"dependencies": {
"@ldo/dataset": "^0.0.1-alpha.23",
"@ldo/rdf-utils": "^0.0.1-alpha.23"
"@ldo/dataset": "^0.0.1-alpha.24",
"@ldo/rdf-utils": "^0.0.1-alpha.24"
},
"files": [
"dist",

@ -11,7 +11,7 @@ describe("createExtendedDatasetFromSerializedInput", () => {
const dataset = await serializedToSubscribableDataset(
JSON.stringify(jsonLdData),
{
format: "application/json-ld",
format: "application/ld+json",
},
);
expect(dataset.size).toBe(9);

@ -1,6 +1,6 @@
{
"name": "@ldo/traverser-shexj",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.24",
"description": "A type-traverser for ShexJ",
"main": "dist/index.js",
"scripts": {
@ -30,7 +30,7 @@
"src"
],
"dependencies": {
"@ldo/type-traverser": "^0.0.1-alpha.23"
"@ldo/type-traverser": "^0.0.1-alpha.24"
},
"publishConfig": {
"access": "public"

@ -1,6 +1,6 @@
{
"name": "@ldo/type-traverser",
"version": "0.0.1-alpha.23",
"version": "0.0.1-alpha.24",
"description": "An organized way to traverse over objects using typescript",
"main": "dist/index.js",
"scripts": {

Loading…
Cancel
Save