mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
refactor: Rename WebHook to Webhook
This commit is contained in:
parent
1e3684bcf3
commit
531c299c7b
@ -31,6 +31,8 @@ The following changes are relevant for v6 custom configs that replaced certain f
|
|||||||
- `/http/notifications/base/storage.json`
|
- `/http/notifications/base/storage.json`
|
||||||
- `/identity/*`
|
- `/identity/*`
|
||||||
- `/storage/keyvalue/storages/storages.json`
|
- `/storage/keyvalue/storages/storages.json`
|
||||||
|
- All identifiers containing the string "WebHook" have been renamed to instead use "Webhook"
|
||||||
|
to be consistent with the notification type.
|
||||||
|
|
||||||
### Interface changes
|
### Interface changes
|
||||||
|
|
||||||
@ -41,6 +43,8 @@ These changes are relevant if you wrote custom modules for the server that depen
|
|||||||
- `EncodingPathStorage` has been removed
|
- `EncodingPathStorage` has been removed
|
||||||
and its functionality split up over `Base64EncodingStorage` and `ContainerPathStorage`.
|
and its functionality split up over `Base64EncodingStorage` and `ContainerPathStorage`.
|
||||||
`HashEncodingPathStorage` has similarly been replaced by introducing `HashEncodingStorage`.
|
`HashEncodingPathStorage` has similarly been replaced by introducing `HashEncodingStorage`.
|
||||||
|
- All classes with the name `WebHook*` have been renamed to `Webhook*`
|
||||||
|
to be consistent with the corresponding notification type.
|
||||||
|
|
||||||
## v6.1.0
|
## v6.1.0
|
||||||
|
|
||||||
|
@ -22,13 +22,13 @@ Determines how notifications should be sent out from the server when resources c
|
|||||||
|
|
||||||
* *all*: Supports all available notification types of the Solid Notifications protocol
|
* *all*: Supports all available notification types of the Solid Notifications protocol
|
||||||
[specification](https://solidproject.org/TR/notifications-protocol).
|
[specification](https://solidproject.org/TR/notifications-protocol).
|
||||||
Currently, this includes WebHookChannel2023 and WebSocketChannel2023.
|
Currently, this includes WebhookChannel2023 and WebSocketChannel2023.
|
||||||
* *disabled*: No notifications are sent out.
|
* *disabled*: No notifications are sent out.
|
||||||
* *legacy-websocket*: Follows the legacy Solid WebSocket
|
* *legacy-websocket*: Follows the legacy Solid WebSocket
|
||||||
[specification](https://github.com/solid/solid-spec/blob/master/api-websockets.md).
|
[specification](https://github.com/solid/solid-spec/blob/master/api-websockets.md).
|
||||||
Will be removed in future versions.
|
Will be removed in future versions.
|
||||||
* *new-old-websockets.json*: Support for both the legacy Solid Websockets and the new WebSocketChannel2023.
|
* *new-old-websockets.json*: Support for both the legacy Solid Websockets and the new WebSocketChannel2023.
|
||||||
* *webhooks*: Follows the WebHookChannel2023
|
* *webhooks*: Follows the WebhookChannel2023
|
||||||
[specification](https://solid.github.io/notifications/webhook-channel-2023) draft.
|
[specification](https://solid.github.io/notifications/webhook-channel-2023) draft.
|
||||||
* *websockets*: Follows the WebSocketChannel2023
|
* *websockets*: Follows the WebSocketChannel2023
|
||||||
[specification](https://solid.github.io/notifications/websocket-channel-2023).
|
[specification](https://solid.github.io/notifications/websocket-channel-2023).
|
||||||
|
@ -3,23 +3,23 @@
|
|||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
"comment": "Handles the generation and serialization of notifications for WebhookChannel2023.",
|
"comment": "Handles the generation and serialization of notifications for WebhookChannel2023.",
|
||||||
"@id": "urn:solid-server:default:WebHookNotificationHandler",
|
"@id": "urn:solid-server:default:WebhookNotificationHandler",
|
||||||
"@type": "TypedNotificationHandler",
|
"@type": "TypedNotificationHandler",
|
||||||
"type": "http://www.w3.org/ns/solid/notifications#WebhookChannel2023",
|
"type": "http://www.w3.org/ns/solid/notifications#WebhookChannel2023",
|
||||||
"source": {
|
"source": {
|
||||||
"@type": "ComposedNotificationHandler",
|
"@type": "ComposedNotificationHandler",
|
||||||
"generator": { "@id": "urn:solid-server:default:BaseNotificationGenerator" },
|
"generator": { "@id": "urn:solid-server:default:BaseNotificationGenerator" },
|
||||||
"serializer": { "@id": "urn:solid-server:default:BaseNotificationSerializer" },
|
"serializer": { "@id": "urn:solid-server:default:BaseNotificationSerializer" },
|
||||||
"emitter": { "@id": "urn:solid-server:default:WebHookEmitter" },
|
"emitter": { "@id": "urn:solid-server:default:WebhookEmitter" },
|
||||||
"eTagHandler": { "@id": "urn:solid-server:default:ETagHandler" }
|
"eTagHandler": { "@id": "urn:solid-server:default:ETagHandler" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"comment": "Emits serialized notifications through HTTP requests to the Webhook.",
|
"comment": "Emits serialized notifications through HTTP requests to the Webhook.",
|
||||||
"@id": "urn:solid-server:default:WebHookEmitter",
|
"@id": "urn:solid-server:default:WebhookEmitter",
|
||||||
"@type": "WebHookEmitter",
|
"@type": "WebhookEmitter",
|
||||||
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||||
"webIdRoute": { "@id": "urn:solid-server:default:WebHookWebIdRoute" },
|
"webIdRoute": { "@id": "urn:solid-server:default:WebhookWebIdRoute" },
|
||||||
"jwkGenerator": { "@id": "urn:solid-server:default:JwkGenerator" }
|
"jwkGenerator": { "@id": "urn:solid-server:default:JwkGenerator" }
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -27,7 +27,7 @@
|
|||||||
"@id": "urn:solid-server:default:NotificationHandler",
|
"@id": "urn:solid-server:default:NotificationHandler",
|
||||||
"@type": "WaterfallHandler",
|
"@type": "WaterfallHandler",
|
||||||
"handlers": [
|
"handlers": [
|
||||||
{ "@id": "urn:solid-server:default:WebHookNotificationHandler" }
|
{ "@id": "urn:solid-server:default:WebhookNotificationHandler" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -2,26 +2,26 @@
|
|||||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:WebHookRoute",
|
"@id": "urn:solid-server:default:WebhookRoute",
|
||||||
"@type": "RelativePathInteractionRoute",
|
"@type": "RelativePathInteractionRoute",
|
||||||
"base": { "@id": "urn:solid-server:default:NotificationRoute" },
|
"base": { "@id": "urn:solid-server:default:NotificationRoute" },
|
||||||
"relativePath": "/WebhookChannel2023/"
|
"relativePath": "/WebhookChannel2023/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:WebHookWebIdRoute",
|
"@id": "urn:solid-server:default:WebhookWebIdRoute",
|
||||||
"@type": "RelativePathInteractionRoute",
|
"@type": "RelativePathInteractionRoute",
|
||||||
"base": { "@id": "urn:solid-server:default:WebHookRoute" },
|
"base": { "@id": "urn:solid-server:default:WebhookRoute" },
|
||||||
"relativePath": "/webId"
|
"relativePath": "/webId"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"comment": "Handles the WebhookChannel2023 WebID.",
|
"comment": "Handles the WebhookChannel2023 WebID.",
|
||||||
"@id": "urn:solid-server:default:WebHookWebId",
|
"@id": "urn:solid-server:default:WebhookWebId",
|
||||||
"@type": "OperationRouterHandler",
|
"@type": "OperationRouterHandler",
|
||||||
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||||
"allowedPathNames": [ "/WebhookChannel2023/webId$" ],
|
"allowedPathNames": [ "/WebhookChannel2023/webId$" ],
|
||||||
"handler": {
|
"handler": {
|
||||||
"@type": "WebHookWebId",
|
"@type": "WebhookWebId",
|
||||||
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
|
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -30,8 +30,8 @@
|
|||||||
"@id": "urn:solid-server:default:NotificationTypeHandler",
|
"@id": "urn:solid-server:default:NotificationTypeHandler",
|
||||||
"@type": "WaterfallHandler",
|
"@type": "WaterfallHandler",
|
||||||
"handlers": [
|
"handlers": [
|
||||||
{ "@id": "urn:solid-server:default:WebHookRouter" },
|
{ "@id": "urn:solid-server:default:WebhookRouter" },
|
||||||
{ "@id": "urn:solid-server:default:WebHookWebId" }
|
{ "@id": "urn:solid-server:default:WebhookWebId" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
"comment": "Handles the subscriptions targeting a WebhookChannel2023.",
|
"comment": "Handles the subscriptions targeting a WebhookChannel2023.",
|
||||||
"@id": "urn:solid-server:default:WebHookRouter",
|
"@id": "urn:solid-server:default:WebhookRouter",
|
||||||
"@type": "OperationRouterHandler",
|
"@type": "OperationRouterHandler",
|
||||||
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||||
"allowedMethods": [ "HEAD", "GET", "POST" ],
|
"allowedMethods": [ "HEAD", "GET", "POST" ],
|
||||||
"allowedPathNames": [ "/WebhookChannel2023/$" ],
|
"allowedPathNames": [ "/WebhookChannel2023/$" ],
|
||||||
"handler": {
|
"handler": {
|
||||||
"@id": "urn:solid-server:default:WebHookSubscriber",
|
"@id": "urn:solid-server:default:WebhookSubscriber",
|
||||||
"@type": "NotificationSubscriber",
|
"@type": "NotificationSubscriber",
|
||||||
"channelType": { "@id": "urn:solid-server:default:WebHookChannel2023Type" },
|
"channelType": { "@id": "urn:solid-server:default:WebhookChannel2023Type" },
|
||||||
"converter": { "@id": "urn:solid-server:default:RepresentationConverter" },
|
"converter": { "@id": "urn:solid-server:default:RepresentationConverter" },
|
||||||
"credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" },
|
"credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" },
|
||||||
"permissionReader": { "@id": "urn:solid-server:default:PermissionReader" },
|
"permissionReader": { "@id": "urn:solid-server:default:PermissionReader" },
|
||||||
@ -21,13 +21,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"comment": "Contains all the metadata relevant for a WebhookChannel2023.",
|
"comment": "Contains all the metadata relevant for a WebhookChannel2023.",
|
||||||
"@id": "urn:solid-server:default:WebHookChannel2023Type",
|
"@id": "urn:solid-server:default:WebhookChannel2023Type",
|
||||||
"@type": "WebhookChannel2023Type",
|
"@type": "WebhookChannel2023Type",
|
||||||
"route": { "@id": "urn:solid-server:default:WebHookRoute" },
|
"route": { "@id": "urn:solid-server:default:WebhookRoute" },
|
||||||
"webIdRoute": { "@id": "urn:solid-server:default:WebHookWebIdRoute" },
|
"webIdRoute": { "@id": "urn:solid-server:default:WebhookWebIdRoute" },
|
||||||
"stateHandler": {
|
"stateHandler": {
|
||||||
"@type": "BaseStateHandler",
|
"@type": "BaseStateHandler",
|
||||||
"handler": { "@id": "urn:solid-server:default:WebHookNotificationHandler" },
|
"handler": { "@id": "urn:solid-server:default:WebhookNotificationHandler" },
|
||||||
"storage": { "@id": "urn:solid-server:default:SubscriptionStorage" }
|
"storage": { "@id": "urn:solid-server:default:SubscriptionStorage" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"@type": "NotificationDescriber",
|
"@type": "NotificationDescriber",
|
||||||
"subscriptions": [
|
"subscriptions": [
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:WebHookChannel2023Type"
|
"@id": "urn:solid-server:default:WebhookChannel2023Type"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -174,13 +174,13 @@ so that class can emit events later on, as mentioned above.
|
|||||||
The state handler will make sure that a notification gets sent out if the subscription has a `state` feature request,
|
The state handler will make sure that a notification gets sent out if the subscription has a `state` feature request,
|
||||||
as defined in the notification specification.
|
as defined in the notification specification.
|
||||||
|
|
||||||
## WebHookChannel2023
|
## WebhookChannel2023
|
||||||
|
|
||||||
The additions required to support
|
The additions required to support
|
||||||
[WebHookChannel2023](https://solid.github.io/notifications/webhook-channel-2023)
|
[WebhookChannel2023](https://solid.github.io/notifications/webhook-channel-2023)
|
||||||
are quite similar to those needed for WebSocketChannel2023:
|
are quite similar to those needed for WebSocketChannel2023:
|
||||||
|
|
||||||
* For discovery, there is a `WebHookDescriber`, which is an extension of a `NotificationDescriber`.
|
* For discovery, there is a `WebhookDescriber`, which is an extension of a `NotificationDescriber`.
|
||||||
* The `WebHookChannel2023Type` class contains all the necessary typing information.
|
* The `WebhookChannel2023Type` class contains all the necessary typing information.
|
||||||
* `WebHookEmitter` is the `NotificationEmitter` that sends the request.
|
* `WebhookEmitter` is the `NotificationEmitter` that sends the request.
|
||||||
* `WebHookUnsubscriber` and `WebHookWebId` are additional utility classes to support the spec requirements.
|
* `WebhookUnsubscriber` and `WebhookWebId` are additional utility classes to support the spec requirements.
|
||||||
|
@ -228,4 +228,4 @@ You can modify this behaviour by adding the following block to your configuratio
|
|||||||
`maxDuration` defines after how many minutes every channel will be removed.
|
`maxDuration` defines after how many minutes every channel will be removed.
|
||||||
Setting this value to 0 will allow channels to exist forever.
|
Setting this value to 0 will allow channels to exist forever.
|
||||||
Similarly, to change the maximum duration of webhook channels you can use the identifier
|
Similarly, to change the maximum duration of webhook channels you can use the identifier
|
||||||
`urn:solid-server:default:WebHookSubscriber`.
|
`urn:solid-server:default:WebhookSubscriber`.
|
||||||
|
@ -325,10 +325,10 @@ export * from './server/notifications/serialize/ConvertingNotificationSerializer
|
|||||||
export * from './server/notifications/serialize/JsonLdNotificationSerializer';
|
export * from './server/notifications/serialize/JsonLdNotificationSerializer';
|
||||||
export * from './server/notifications/serialize/NotificationSerializer';
|
export * from './server/notifications/serialize/NotificationSerializer';
|
||||||
|
|
||||||
// Server/Notifications/WebHookChannel2023
|
// Server/Notifications/WebhookChannel2023
|
||||||
export * from './server/notifications/WebHookChannel2023/WebhookChannel2023Type';
|
export * from './server/notifications/WebhookChannel2023/WebhookChannel2023Type';
|
||||||
export * from './server/notifications/WebHookChannel2023/WebHookEmitter';
|
export * from './server/notifications/WebhookChannel2023/WebhookEmitter';
|
||||||
export * from './server/notifications/WebHookChannel2023/WebHookWebId';
|
export * from './server/notifications/WebhookChannel2023/WebhookWebId';
|
||||||
|
|
||||||
// Server/Notifications/WebSocketChannel2023
|
// Server/Notifications/WebSocketChannel2023
|
||||||
export * from './server/notifications/WebSocketChannel2023/WebSocket2023Emitter';
|
export * from './server/notifications/WebSocketChannel2023/WebSocket2023Emitter';
|
||||||
|
@ -8,11 +8,11 @@ import type { NotificationChannel } from '../NotificationChannel';
|
|||||||
import type { StateHandler } from '../StateHandler';
|
import type { StateHandler } from '../StateHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link NotificationChannel} containing the necessary fields for a WebHookChannel2023 channel.
|
* A {@link NotificationChannel} containing the necessary fields for a WebhookChannel2023 channel.
|
||||||
*/
|
*/
|
||||||
export interface WebhookChannel2023 extends NotificationChannel {
|
export interface WebhookChannel2023 extends NotificationChannel {
|
||||||
/**
|
/**
|
||||||
* The "WebHookChannel2023" type.
|
* The "WebhookChannel2023" type.
|
||||||
*/
|
*/
|
||||||
type: typeof NOTIFY.WebhookChannel2023;
|
type: typeof NOTIFY.WebhookChannel2023;
|
||||||
/**
|
/**
|
||||||
@ -21,12 +21,12 @@ export interface WebhookChannel2023 extends NotificationChannel {
|
|||||||
sendTo: string;
|
sendTo: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isWebHook2023Channel(channel: NotificationChannel): channel is WebhookChannel2023 {
|
export function isWebhook2023Channel(channel: NotificationChannel): channel is WebhookChannel2023 {
|
||||||
return channel.type === NOTIFY.WebhookChannel2023;
|
return channel.type === NOTIFY.WebhookChannel2023;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The notification channel type WebHookChannel2023 as described in
|
* The notification channel type WebhookChannel2023 as described in
|
||||||
* https://solid.github.io/notifications/webhook-channel-2023
|
* https://solid.github.io/notifications/webhook-channel-2023
|
||||||
*
|
*
|
||||||
* Requires read permissions on a resource to be able to receive notifications.
|
* Requires read permissions on a resource to be able to receive notifications.
|
@ -10,10 +10,10 @@ import { readableToString } from '../../../util/StreamUtil';
|
|||||||
import type { NotificationEmitterInput } from '../NotificationEmitter';
|
import type { NotificationEmitterInput } from '../NotificationEmitter';
|
||||||
import { NotificationEmitter } from '../NotificationEmitter';
|
import { NotificationEmitter } from '../NotificationEmitter';
|
||||||
import type { WebhookChannel2023 } from './WebhookChannel2023Type';
|
import type { WebhookChannel2023 } from './WebhookChannel2023Type';
|
||||||
import { isWebHook2023Channel } from './WebhookChannel2023Type';
|
import { isWebhook2023Channel } from './WebhookChannel2023Type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits a notification representation using the WebHookChannel2023 specification.
|
* Emits a notification representation using the WebhookChannel2023 specification.
|
||||||
*
|
*
|
||||||
* At the time of writing it is not specified how exactly a notification sender should make its requests verifiable,
|
* At the time of writing it is not specified how exactly a notification sender should make its requests verifiable,
|
||||||
* so for now we use a token similar to those from Solid-OIDC, signed by the server itself.
|
* so for now we use a token similar to those from Solid-OIDC, signed by the server itself.
|
||||||
@ -23,7 +23,7 @@ import { isWebHook2023Channel } from './WebhookChannel2023Type';
|
|||||||
* The `expiration` input parameter is how long the generated token should be valid in minutes.
|
* The `expiration` input parameter is how long the generated token should be valid in minutes.
|
||||||
* Default is 20.
|
* Default is 20.
|
||||||
*/
|
*/
|
||||||
export class WebHookEmitter extends NotificationEmitter {
|
export class WebhookEmitter extends NotificationEmitter {
|
||||||
protected readonly logger = getLoggerFor(this);
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
|
||||||
private readonly issuer: string;
|
private readonly issuer: string;
|
||||||
@ -40,15 +40,15 @@ export class WebHookEmitter extends NotificationEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async canHandle({ channel }: NotificationEmitterInput): Promise<void> {
|
public async canHandle({ channel }: NotificationEmitterInput): Promise<void> {
|
||||||
if (!isWebHook2023Channel(channel)) {
|
if (!isWebhook2023Channel(channel)) {
|
||||||
throw new NotImplementedHttpError(`${channel.id} is not a WebHookChannel2023 channel.`);
|
throw new NotImplementedHttpError(`${channel.id} is not a WebhookChannel2023 channel.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle({ channel, representation }: NotificationEmitterInput): Promise<void> {
|
public async handle({ channel, representation }: NotificationEmitterInput): Promise<void> {
|
||||||
// Cast was checked in `canHandle`
|
// Cast was checked in `canHandle`
|
||||||
const webHookChannel = channel as WebhookChannel2023;
|
const webhookChannel = channel as WebhookChannel2023;
|
||||||
this.logger.debug(`Emitting WebHook notification with target ${webHookChannel.sendTo}`);
|
this.logger.debug(`Emitting Webhook notification with target ${webhookChannel.sendTo}`);
|
||||||
|
|
||||||
const privateKey = await this.jwkGenerator.getPrivateKey();
|
const privateKey = await this.jwkGenerator.getPrivateKey();
|
||||||
const publicKey = await this.jwkGenerator.getPublicKey();
|
const publicKey = await this.jwkGenerator.getPublicKey();
|
||||||
@ -78,14 +78,14 @@ export class WebHookEmitter extends NotificationEmitter {
|
|||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop#section-4.2
|
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop#section-4.2
|
||||||
const dpopProof = await new SignJWT({
|
const dpopProof = await new SignJWT({
|
||||||
htu: webHookChannel.sendTo,
|
htu: webhookChannel.sendTo,
|
||||||
htm: 'POST',
|
htm: 'POST',
|
||||||
}).setProtectedHeader({ alg: privateKey.alg, jwk: publicKey, typ: 'dpop+jwt' })
|
}).setProtectedHeader({ alg: privateKey.alg, jwk: publicKey, typ: 'dpop+jwt' })
|
||||||
.setIssuedAt(time)
|
.setIssuedAt(time)
|
||||||
.setJti(v4())
|
.setJti(v4())
|
||||||
.sign(privateKeyObject);
|
.sign(privateKeyObject);
|
||||||
|
|
||||||
const response = await fetch(webHookChannel.sendTo, {
|
const response = await fetch(webhookChannel.sendTo, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': representation.metadata.contentType!,
|
'content-type': representation.metadata.contentType!,
|
||||||
@ -95,7 +95,7 @@ export class WebHookEmitter extends NotificationEmitter {
|
|||||||
body: await readableToString(representation.data),
|
body: await readableToString(representation.data),
|
||||||
});
|
});
|
||||||
if (response.status >= 400) {
|
if (response.status >= 400) {
|
||||||
this.logger.error(`There was an issue emitting a WebHook notification with target ${webHookChannel.sendTo}: ${
|
this.logger.error(`There was an issue emitting a Webhook notification with target ${webhookChannel.sendTo}: ${
|
||||||
await response.text()}`);
|
await response.text()}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,11 +9,11 @@ import type { OperationHttpHandlerInput } from '../../OperationHttpHandler';
|
|||||||
import { OperationHttpHandler } from '../../OperationHttpHandler';
|
import { OperationHttpHandler } from '../../OperationHttpHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a fixed WebID that we use to identify the server for notifications sent using a WebHookChannel2023.
|
* Generates a fixed WebID that we use to identify the server for notifications sent using a WebhookChannel2023.
|
||||||
* This is used in tandem with the tokens generated by the {@link WebHookEmitter}.
|
* This is used in tandem with the tokens generated by the {@link WebhookEmitter}.
|
||||||
* This is a minimal WebID with only the `solid:oidcIssuer` triple.
|
* This is a minimal WebID with only the `solid:oidcIssuer` triple.
|
||||||
*/
|
*/
|
||||||
export class WebHookWebId extends OperationHttpHandler {
|
export class WebhookWebId extends OperationHttpHandler {
|
||||||
private readonly turtle: string;
|
private readonly turtle: string;
|
||||||
|
|
||||||
public constructor(baseUrl: string) {
|
public constructor(baseUrl: string) {
|
@ -21,14 +21,14 @@ import {
|
|||||||
} from './Config';
|
} from './Config';
|
||||||
import quad = DataFactory.quad;
|
import quad = DataFactory.quad;
|
||||||
|
|
||||||
const port = getPort('WebHookChannel2023');
|
const port = getPort('WebhookChannel2023');
|
||||||
const baseUrl = `http://localhost:${port}/`;
|
const baseUrl = `http://localhost:${port}/`;
|
||||||
const clientPort = getPort('WebHookChannel2023-client');
|
const clientPort = getPort('WebhookChannel2023-client');
|
||||||
const target = `http://localhost:${clientPort}/`;
|
const target = `http://localhost:${clientPort}/`;
|
||||||
const webId = 'http://example.com/card/#me';
|
const webId = 'http://example.com/card/#me';
|
||||||
const notificationType = NOTIFY.WebhookChannel2023;
|
const notificationType = NOTIFY.WebhookChannel2023;
|
||||||
|
|
||||||
const rootFilePath = getTestFolder('WebHookChannel2023');
|
const rootFilePath = getTestFolder('WebhookChannel2023');
|
||||||
const stores: [string, any][] = [
|
const stores: [string, any][] = [
|
||||||
[ 'in-memory storage', {
|
[ 'in-memory storage', {
|
||||||
configs: [ 'storage/backend/memory.json', 'util/resource-locker/memory.json' ],
|
configs: [ 'storage/backend/memory.json', 'util/resource-locker/memory.json' ],
|
||||||
@ -40,7 +40,7 @@ const stores: [string, any][] = [
|
|||||||
}],
|
}],
|
||||||
];
|
];
|
||||||
|
|
||||||
describe.each(stores)('A server supporting WebHookChannel2023 using %s', (name, { configs, teardown }): void => {
|
describe.each(stores)('A server supporting WebhookChannel2023 using %s', (name, { configs, teardown }): void => {
|
||||||
let app: App;
|
let app: App;
|
||||||
const topic = joinUrl(baseUrl, '/foo');
|
const topic = joinUrl(baseUrl, '/foo');
|
||||||
let storageDescriptionUrl: string;
|
let storageDescriptionUrl: string;
|
@ -12,11 +12,11 @@ import type { NotificationChannel } from '../../../../../src/server/notification
|
|||||||
import type { StateHandler } from '../../../../../src/server/notifications/StateHandler';
|
import type { StateHandler } from '../../../../../src/server/notifications/StateHandler';
|
||||||
import type {
|
import type {
|
||||||
WebhookChannel2023,
|
WebhookChannel2023,
|
||||||
} from '../../../../../src/server/notifications/WebHookChannel2023/WebhookChannel2023Type';
|
} from '../../../../../src/server/notifications/WebhookChannel2023/WebhookChannel2023Type';
|
||||||
import {
|
import {
|
||||||
isWebHook2023Channel,
|
isWebhook2023Channel,
|
||||||
WebhookChannel2023Type,
|
WebhookChannel2023Type,
|
||||||
} from '../../../../../src/server/notifications/WebHookChannel2023/WebhookChannel2023Type';
|
} from '../../../../../src/server/notifications/WebhookChannel2023/WebhookChannel2023Type';
|
||||||
import { NOTIFY, RDF } from '../../../../../src/util/Vocabularies';
|
import { NOTIFY, RDF } from '../../../../../src/util/Vocabularies';
|
||||||
import quad = DataFactory.quad;
|
import quad = DataFactory.quad;
|
||||||
import blankNode = DataFactory.blankNode;
|
import blankNode = DataFactory.blankNode;
|
||||||
@ -63,10 +63,10 @@ describe('A WebhookChannel2023Type', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('exposes a utility function to verify if a channel is a webhook channel.', async(): Promise<void> => {
|
it('exposes a utility function to verify if a channel is a webhook channel.', async(): Promise<void> => {
|
||||||
expect(isWebHook2023Channel(channel)).toBe(true);
|
expect(isWebhook2023Channel(channel)).toBe(true);
|
||||||
|
|
||||||
(channel as NotificationChannel).type = 'something else';
|
(channel as NotificationChannel).type = 'something else';
|
||||||
expect(isWebHook2023Channel(channel)).toBe(false);
|
expect(isWebhook2023Channel(channel)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly parses notification channel bodies.', async(): Promise<void> => {
|
it('correctly parses notification channel bodies.', async(): Promise<void> => {
|
@ -11,8 +11,8 @@ import { getLoggerFor } from '../../../../../src/logging/LogUtil';
|
|||||||
import type { Notification } from '../../../../../src/server/notifications/Notification';
|
import type { Notification } from '../../../../../src/server/notifications/Notification';
|
||||||
import type {
|
import type {
|
||||||
WebhookChannel2023,
|
WebhookChannel2023,
|
||||||
} from '../../../../../src/server/notifications/WebHookChannel2023/WebhookChannel2023Type';
|
} from '../../../../../src/server/notifications/WebhookChannel2023/WebhookChannel2023Type';
|
||||||
import { WebHookEmitter } from '../../../../../src/server/notifications/WebHookChannel2023/WebHookEmitter';
|
import { WebhookEmitter } from '../../../../../src/server/notifications/WebhookChannel2023/WebhookEmitter';
|
||||||
import { NotImplementedHttpError } from '../../../../../src/util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../../../../../src/util/errors/NotImplementedHttpError';
|
||||||
import { matchesAuthorizationScheme } from '../../../../../src/util/HeaderUtil';
|
import { matchesAuthorizationScheme } from '../../../../../src/util/HeaderUtil';
|
||||||
import { trimTrailingSlashes } from '../../../../../src/util/PathUtil';
|
import { trimTrailingSlashes } from '../../../../../src/util/PathUtil';
|
||||||
@ -26,7 +26,7 @@ jest.mock('../../../../../src/logging/LogUtil', (): any => {
|
|||||||
return { getLoggerFor: (): Logger => logger };
|
return { getLoggerFor: (): Logger => logger };
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('A WebHookEmitter', (): void => {
|
describe('A WebhookEmitter', (): void => {
|
||||||
const fetchMock: jest.Mock = fetch as any;
|
const fetchMock: jest.Mock = fetch as any;
|
||||||
const baseUrl = 'http://example.com/';
|
const baseUrl = 'http://example.com/';
|
||||||
const serverWebId = 'http://example.com/.notifcations/webhooks/webid';
|
const serverWebId = 'http://example.com/.notifcations/webhooks/webid';
|
||||||
@ -52,7 +52,7 @@ describe('A WebHookEmitter', (): void => {
|
|||||||
let privateJwk: AlgJwk;
|
let privateJwk: AlgJwk;
|
||||||
let publicJwk: AlgJwk;
|
let publicJwk: AlgJwk;
|
||||||
let jwkGenerator: jest.Mocked<JwkGenerator>;
|
let jwkGenerator: jest.Mocked<JwkGenerator>;
|
||||||
let emitter: WebHookEmitter;
|
let emitter: WebhookEmitter;
|
||||||
|
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
fetchMock.mockResolvedValue({ status: 200 });
|
fetchMock.mockResolvedValue({ status: 200 });
|
||||||
@ -70,7 +70,7 @@ describe('A WebHookEmitter', (): void => {
|
|||||||
getPublicKey: jest.fn().mockResolvedValue(publicJwk),
|
getPublicKey: jest.fn().mockResolvedValue(publicJwk),
|
||||||
};
|
};
|
||||||
|
|
||||||
emitter = new WebHookEmitter(baseUrl, webIdRoute, jwkGenerator);
|
emitter = new WebhookEmitter(baseUrl, webIdRoute, jwkGenerator);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors if the channel type is wrong.', async(): Promise<void> => {
|
it('errors if the channel type is wrong.', async(): Promise<void> => {
|
||||||
@ -139,7 +139,7 @@ describe('A WebHookEmitter', (): void => {
|
|||||||
|
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
expect(logger.error).toHaveBeenLastCalledWith(
|
expect(logger.error).toHaveBeenLastCalledWith(
|
||||||
`There was an issue emitting a WebHook notification with target ${channel.sendTo}: invalid request`,
|
`There was an issue emitting a Webhook notification with target ${channel.sendTo}: invalid request`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -3,17 +3,17 @@ import type { Operation } from '../../../../../src/http/Operation';
|
|||||||
import { BasicRepresentation } from '../../../../../src/http/representation/BasicRepresentation';
|
import { BasicRepresentation } from '../../../../../src/http/representation/BasicRepresentation';
|
||||||
import type { HttpRequest } from '../../../../../src/server/HttpRequest';
|
import type { HttpRequest } from '../../../../../src/server/HttpRequest';
|
||||||
import type { HttpResponse } from '../../../../../src/server/HttpResponse';
|
import type { HttpResponse } from '../../../../../src/server/HttpResponse';
|
||||||
import { WebHookWebId } from '../../../../../src/server/notifications/WebHookChannel2023/WebHookWebId';
|
import { WebhookWebId } from '../../../../../src/server/notifications/WebhookChannel2023/WebhookWebId';
|
||||||
import { readableToString } from '../../../../../src/util/StreamUtil';
|
import { readableToString } from '../../../../../src/util/StreamUtil';
|
||||||
import { SOLID } from '../../../../../src/util/Vocabularies';
|
import { SOLID } from '../../../../../src/util/Vocabularies';
|
||||||
const { namedNode, quad } = DataFactory;
|
const { namedNode, quad } = DataFactory;
|
||||||
|
|
||||||
describe('A WebHookWebId', (): void => {
|
describe('A WebhookWebId', (): void => {
|
||||||
const request: HttpRequest = {} as any;
|
const request: HttpRequest = {} as any;
|
||||||
const response: HttpResponse = {} as any;
|
const response: HttpResponse = {} as any;
|
||||||
let operation: Operation;
|
let operation: Operation;
|
||||||
const baseUrl = 'http://example.com/';
|
const baseUrl = 'http://example.com/';
|
||||||
let webIdHandler: WebHookWebId;
|
let webIdHandler: WebhookWebId;
|
||||||
|
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
operation = {
|
operation = {
|
||||||
@ -23,7 +23,7 @@ describe('A WebHookWebId', (): void => {
|
|||||||
body: new BasicRepresentation(),
|
body: new BasicRepresentation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
webIdHandler = new WebHookWebId(baseUrl);
|
webIdHandler = new WebhookWebId(baseUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a solid:oidcIssuer triple.', async(): Promise<void> => {
|
it('returns a solid:oidcIssuer triple.', async(): Promise<void> => {
|
||||||
@ -41,7 +41,7 @@ describe('A WebHookWebId', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('errors if the base URL is invalid.', async(): Promise<void> => {
|
it('errors if the base URL is invalid.', async(): Promise<void> => {
|
||||||
expect((): any => new WebHookWebId('very invalid URL'))
|
expect((): any => new WebhookWebId('very invalid URL'))
|
||||||
.toThrow('Invalid issuer URL: Unexpected "<very" on line 2.');
|
.toThrow('Invalid issuer URL: Unexpected "<very" on line 2.');
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -29,8 +29,8 @@ const portNames = [
|
|||||||
'SetupMemory',
|
'SetupMemory',
|
||||||
'SparqlStorage',
|
'SparqlStorage',
|
||||||
'Subdomains',
|
'Subdomains',
|
||||||
'WebHookChannel2023',
|
'WebhookChannel2023',
|
||||||
'WebHookChannel2023-client',
|
'WebhookChannel2023-client',
|
||||||
'WebSocketChannel2023',
|
'WebSocketChannel2023',
|
||||||
|
|
||||||
// Unit
|
// Unit
|
||||||
|
Loading…
x
Reference in New Issue
Block a user