Skip to content

Commit

Permalink
[discovery] first draft of DiscoveryClient
Browse files Browse the repository at this point in the history
  • Loading branch information
elf-pavlik committed Jun 26, 2023
1 parent eea36e0 commit cdc40f8
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 27 deletions.
6 changes: 5 additions & 1 deletion packages/discovery/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@
},
"devDependencies": {
"@solid-notifications/types": "^0.0.0",
"@solid/community-server": "^5.1.0",
"@solid/community-server": "^6.0.1",
"@types/jest": "^29.5.0",
"jest": "^29.5.0",
"solid-test-utils": "^0.0.0",
"ts-jest": "^29.1.0"
},
"dependencies": {
"@janeirodigital/interop-utils": "^1.0.0-rc.20",
"n3": "^1.16.4"
}
}
66 changes: 66 additions & 0 deletions packages/discovery/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { DataFactory } from 'n3'
import { parseTurtle, getDescriptionResource, getOneMatchingQuad, getAllMatchingQuads, NOTIFY, getStorageDescription } from '@janeirodigital/interop-utils'
import type { DatasetCore } from '@rdfjs/types'
import { ChannelType, SubscriptionService } from '@solid-notifications/types'

export class DiscoveryClient {

private parser: {
contentType: 'text/turtle' | 'application/ld+json',
parse: typeof parseTurtle
}
constructor(private authnFetch: typeof fetch) {
// TODO pass Turtle or JSON-LD parser and set accordignly
this.parser = {
contentType: 'text/turtle',
parse: parseTurtle
}
}

async findService(resourceUri: string, channelType: ChannelType): Promise<SubscriptionService | null> {

const storageDescription = await this.fetchStorageDescription(resourceUri)
console.log([...storageDescription])

// TODO handle multiple matching services
const serviceNode = getOneMatchingQuad(storageDescription, null, NOTIFY.channelType, DataFactory.namedNode(channelType))?.subject
if (!serviceNode) return null
const features = getAllMatchingQuads(storageDescription, serviceNode, NOTIFY.feature).map(quad => quad.object.value)
return {
id: serviceNode.value,
channelType,
feature: features
}
}


// TODO use some rdf-fetch util
async fetchResource(resourceUri): Promise<DatasetCore> {
const response = await this.authnFetch(resourceUri, {
headers: {
'Accept': this.parser.contentType
}
})
return this.parser.parse(await response.text(), response.url)
}

async discoverStorageDescription(resourceUri: string): Promise<string> {
const response = await this.authnFetch(resourceUri, { method: 'head' })
return getStorageDescription(response.headers.get('Link'))
}

async fetchStorageDescription(resourceUri): Promise<DatasetCore> {
const storageDescriptionUri = await this.discoverStorageDescription(resourceUri)
return this.fetchResource(storageDescriptionUri)
}

async discoverDescriptionResource(resourceUri: string): Promise<string> {
const response = await this.authnFetch(resourceUri, { method: 'head' })
return getDescriptionResource(response.headers.get('Link'))
}

async fetchDescriptionResource(resourceUri): Promise<DatasetCore> {
const resourceDescriptionUri = await this.discoverDescriptionResource(resourceUri)
return this.fetchResource(resourceDescriptionUri)
}
}
8 changes: 0 additions & 8 deletions packages/discovery/src/getDescribedByUri.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/discovery/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "./getDescribedByUri";
export * from "./client";
60 changes: 60 additions & 0 deletions packages/discovery/test/client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import SolidTestUtils from "solid-test-utils";
import { DiscoveryClient } from "../src/client";
import { DC, NOTIFY } from "@janeirodigital/interop-utils";

describe("discovery", () => {
const stu = new SolidTestUtils();
beforeAll(async () => stu.beforeAll());
afterAll(async () => stu.afterAll());

const cardUri = "http://localhost:3001/example/profile/card"

test("discoverStorageDescription", async () => {
const client = new DiscoveryClient(stu.authFetch)
const storageDescriptionUri = await client.discoverStorageDescription(cardUri)
expect(storageDescriptionUri).toBe('http://localhost:3001/example/.well-known/solid');
});

test("fetchStorageDescription", async () => {
const client = new DiscoveryClient(stu.authFetch)
const dataset = await client.fetchStorageDescription(cardUri)
expect(dataset.match(null, NOTIFY.subscription, NOTIFY.WebhookChannel2023)).toBeTruthy()
});

test("discoverDescriptionResource", async () => {
const client = new DiscoveryClient(stu.authFetch)
const resourceDescriptionUri = await client.discoverDescriptionResource(cardUri)
expect(resourceDescriptionUri).toBe('http://localhost:3001/example/profile/card.meta');
});

test("fetchDescriptionResource", async () => {
const client = new DiscoveryClient(stu.authFetch)
const dataset = await client.fetchDescriptionResource(cardUri)
expect(dataset.match(null, DC.modified)).toBeTruthy()
});

test("find Webhook service", async () => {
const client = new DiscoveryClient(stu.authFetch)
const service = await client.findService(cardUri, NOTIFY.WebhookChannel2023.value)
expect(service).toEqual(expect.objectContaining({
id: 'http://localhost:3001/.notifications/WebhookChannel2023/',
channelType: NOTIFY.WebhookChannel2023.value
}))
});

test("find Web Socket service", async () => {
const client = new DiscoveryClient(stu.authFetch)
const service = await client.findService(cardUri, NOTIFY.WebSocketChannel2023.value)
expect(service).toEqual(expect.objectContaining({
id: 'http://localhost:3001/.notifications/WebSocketChannel2023/',
channelType: NOTIFY.WebSocketChannel2023.value
}))
});

test("find non existing service", async () => {
const client = new DiscoveryClient(stu.authFetch)
//@ts-ignore
const service = await client.findService(cardUri, 'https://fake.example/SomeChannel')
expect(service).toBeNull()
});
});
15 changes: 0 additions & 15 deletions packages/discovery/test/getDescribedByUri.test.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/solid-test-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
"devDependencies": {
"@inrupt/solid-client-authn-core": "^1.14.0",
"@solid/community-server": "^5.1.0",
"@solid/community-server": "^6.0.1",
"node-fetch": "^3.3.1"
}
}
7 changes: 6 additions & 1 deletion packages/types/src/NotificationChannel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export enum ChannelType {
WebhookChannel2023 = 'http://www.w3.org/ns/solid/notifications#WebhookChannel2023',
WebSocketChannel2023 = 'http://www.w3.org/ns/solid/notifications#WebSocketChannel2023',
}

export interface NotificationChannel {
id: string;
type: string;
type: ChannelType; // TODO channel types should be extendible
topic: string | string[];
receiveFrom: string;
sendTo?: string;
Expand Down

0 comments on commit cdc40f8

Please sign in to comment.