Skip to content

Commit 93c08aa

Browse files
committed
[subscription] first draft of SubscriptionClient
1 parent 43e8cc6 commit 93c08aa

File tree

6 files changed

+126
-3
lines changed

6 files changed

+126
-3
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"packages/*"
1010
],
1111
"devDependencies": {
12+
"@types/jest": "^29.5.0",
1213
"@types/node": "^18.15.11",
1314
"@typescript-eslint/eslint-plugin": "^5.57.1",
1415
"@typescript-eslint/parser": "^5.57.1",
@@ -17,6 +18,7 @@
1718
"eslint-plugin-prettier": "^4.2.1",
1819
"jest": "^29.5.0",
1920
"lerna": "^6.6.1",
20-
"typescript": "^5.0.3"
21+
"typescript": "^5.0.3",
22+
"ts-jest": "^29.1.0"
2123
}
2224
}

packages/subscription/jest.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
preset: "ts-jest",
3+
testEnvironment: "node",
4+
coveragePathIgnorePatterns: ["/test/"],
5+
testRegex: ["/test/.*.test.*.ts$"],
6+
};

packages/subscription/package.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,19 @@
2424
"url": "git+https://github.com/o-development/solid-notification-client.git"
2525
},
2626
"scripts": {
27-
"tsc": "tsc"
27+
"tsc": "tsc",
28+
"test": "jest --coverage"
2829
},
2930
"bugs": {
3031
"url": "https://github.com/o-development/solid-notification-client/issues"
32+
},
33+
"devDependencies": {
34+
"@solid-notifications/types": "^0.1.0",
35+
"solid-test-utils": "^0.0.0"
36+
},
37+
"dependencies": {
38+
"@janeirodigital/interop-utils": "^1.0.0-rc.21",
39+
"@solid-notifications/discovery": "^0.1.0",
40+
"n3": "^1.17.0"
3141
}
3242
}

packages/subscription/src/client.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { DataFactory, Store } from 'n3'
2+
import { NOTIFY, RDF, getOneMatchingQuad, parseTurtle, serializeTurtle } from '@janeirodigital/interop-utils'
3+
import { DiscoveryClient } from '@solid-notifications/discovery'
4+
5+
import type { DatasetCore } from '@rdfjs/types'
6+
import type { ChannelType, NotificationChannel } from '@solid-notifications/types'
7+
8+
function buildChannel(topic: string, channelType: ChannelType, sendTo: string): DatasetCore {
9+
const channel = new Store() as DatasetCore
10+
const subject = DataFactory.blankNode()
11+
channel.add(DataFactory.quad(subject, RDF.type, DataFactory.namedNode(channelType)))
12+
channel.add(DataFactory.quad(subject, NOTIFY.topic, DataFactory.namedNode(topic)))
13+
if (sendTo) {
14+
channel.add(DataFactory.quad(subject, NOTIFY.sendTo, DataFactory.namedNode(sendTo)))
15+
}
16+
return channel
17+
}
18+
19+
function formatChannel(dataset: DatasetCore): NotificationChannel {
20+
const subject = getOneMatchingQuad(dataset, null, RDF.type).subject
21+
const channel = {
22+
id: subject.value,
23+
type: getOneMatchingQuad(dataset, subject, RDF.type).object.value as ChannelType, // TODO: improve typing
24+
topic: getOneMatchingQuad(dataset, subject, NOTIFY.topic).object.value,
25+
} as NotificationChannel
26+
const receiveFrom = getOneMatchingQuad(dataset, subject, NOTIFY.receiveFrom)?.object.value
27+
if (receiveFrom) channel.receiveFrom = receiveFrom
28+
const sendTo = getOneMatchingQuad(dataset, subject, NOTIFY.sendTo)?.object.value
29+
if (sendTo) channel.sendTo = sendTo
30+
31+
return channel
32+
}
33+
34+
35+
export class SubscriptionClient {
36+
private rdf: {
37+
contentType: 'text/turtle' | 'application/ld+json',
38+
parse: typeof parseTurtle
39+
serialize: typeof serializeTurtle
40+
}
41+
42+
discovery = new DiscoveryClient(this.authnFetch)
43+
44+
constructor(private authnFetch: typeof fetch) {
45+
// TODO pass Turtle or JSON-LD parser and set accordignly
46+
this.rdf = {
47+
contentType: 'text/turtle',
48+
parse: parseTurtle,
49+
serialize: serializeTurtle
50+
}
51+
}
52+
53+
async subscribe(topic: string, channelType: ChannelType, sendTo?: string): Promise<NotificationChannel> {
54+
// TODO: validate presence of sendTo based on known channel type
55+
const service = await this.discovery.findService(topic, channelType)
56+
const requestedChannel = buildChannel(topic, channelType, sendTo)
57+
const response = await this.authnFetch(service.id, {
58+
method: 'POST',
59+
body: await this.rdf.serialize(requestedChannel),
60+
headers: {
61+
'Accept': this.rdf.contentType,
62+
'Content-Type': this.rdf.contentType
63+
}
64+
})
65+
if(!response.headers.get('Content-Type').startsWith(this.rdf.contentType)) {
66+
throw new Error('unexpected Content Type')
67+
}
68+
const dataset = await this.rdf.parse(await response.text(), response.url)
69+
return formatChannel(dataset)
70+
}
71+
72+
}

packages/subscription/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
console.log("Hello World");
1+
export * from "./client";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import SolidTestUtils from "solid-test-utils";
2+
import { SubscriptionClient } from "../src/client";
3+
import { NOTIFY } from "@janeirodigital/interop-utils";
4+
5+
describe("subscription", () => {
6+
const stu = new SolidTestUtils();
7+
beforeAll(async () => stu.beforeAll());
8+
afterAll(async () => stu.afterAll());
9+
10+
const cardUri = "http://localhost:3001/example/profile/card"
11+
12+
test("subscribe for Webhook", async () => {
13+
const client = new SubscriptionClient(stu.authFetch)
14+
const sendTo = 'https://webhook.example/086b0e2a-25ea-4b94-a3c6-d2ddfcd1e022'
15+
const channel = await client.subscribe(cardUri, NOTIFY.WebhookChannel2023.value, sendTo)
16+
expect(channel).toEqual(expect.objectContaining({
17+
type: NOTIFY.WebhookChannel2023.value,
18+
topic: cardUri,
19+
sendTo
20+
}))
21+
});
22+
23+
test("subscribe for Web Socket", async () => {
24+
const client = new SubscriptionClient(stu.authFetch)
25+
const channel = await client.subscribe(cardUri, NOTIFY.WebSocketChannel2023.value)
26+
expect(channel).toEqual(expect.objectContaining({
27+
type: NOTIFY.WebSocketChannel2023.value,
28+
topic: cardUri,
29+
receiveFrom: expect.stringContaining('ws://localhost:3001/.notifications/WebSocketChannel2023/')
30+
}))
31+
});
32+
33+
});

0 commit comments

Comments
 (0)