Skip to content

Commit

Permalink
Merge pull request #475 from coasys/notifications
Browse files Browse the repository at this point in the history
App Notifications through SDNA triggers 1 - base implementation
  • Loading branch information
lucksus authored May 23, 2024
2 parents 4de38aa + f1bef40 commit fdc7a83
Show file tree
Hide file tree
Showing 22 changed files with 1,201 additions and 300 deletions.
39 changes: 39 additions & 0 deletions core/src/Ad4mClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,45 @@ describe('Ad4mClient', () => {
expect(runtimeInfo.isInitialized).toBe(true);
expect(runtimeInfo.isUnlocked).toBe(true);
})

it('requestInstallNotification smoke test', async () => {
await ad4mClient.runtime.requestInstallNotification({
description: "Test description",
appName: "Test app name",
appUrl: "https://example.com",
appIconPath: "https://example.com/icon",
trigger: "triple(X, ad4m://has_type, flux://message)",
perspectiveIds: ["u983ud-jdhh38d"],
webhookUrl: "https://example.com/webhook",
webhookAuth: "test-auth",
});
})

it('grantNotification smoke test', async () => {
await ad4mClient.runtime.grantNotification("test-notification");
})

it('notifications smoke test', async () => {
const notifications = await ad4mClient.runtime.notifications();
expect(notifications.length).toBe(1);
})

it('updateNotification smoke test', async () => {
await ad4mClient.runtime.updateNotification("test-notification", {
description: "Test description",
appName: "Test app name",
appUrl: "https://example.com",
appIconPath: "https://example.com/icon",
trigger: "triple(X, ad4m://has_type, flux://message)",
perspectiveIds: ["u983ud-jdhh38d"],
webhookUrl: "https://example.com/webhook",
webhookAuth: "test-auth",
});
})

it('removeNotification smoke test', async () => {
await ad4mClient.runtime.removeNotification("test-notification");
})
})

describe('Ad4mClient subscriptions', () => {
Expand Down
1 change: 1 addition & 0 deletions core/src/Exception.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export enum ExceptionType {
ExpressionIsNotVerified = "EXPRESSION_IS_NOT_VERIFIED",
AgentIsUntrusted = "AGENT_IS_UNTRUSTED",
CapabilityRequested = "CAPABILITY_REQUESTED",
InstallNotificationRequest = 'INSTALL_NOTIFICATION_REQUEST',
}
2 changes: 2 additions & 0 deletions core/src/PubSub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const LINK_REMOVED_TOPIC = 'link-removed-topic'
export const LINK_UDATED_TOPIC = 'link-updated-topic'
export const SIGNAL = "signal"
export const EXCEPTION_OCCURRED_TOPIC = "exception-occurred-topic"
export const RUNTIME_NOTIFICATION_REQUESTED_TOPIC = "runtime-notification-requested-topic"
export const RUNTIME_NOTIFICATION_TRIGGERED_TOPIC = "runtime-notification-triggered-topic"
export const NEIGHBOURHOOD_SIGNAL_RECEIVED_TOPIC = "neighbourhood-signal-received-topic"
export const PERSPECTIVE_SYNC_STATE_CHANGE = "perspective-sync-state-change"
export const APPS_CHANGED = "apps-changed"
121 changes: 120 additions & 1 deletion core/src/runtime/RuntimeClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApolloClient, gql } from "@apollo/client/core"
import { Perspective, PerspectiveExpression } from "../perspectives/Perspective"
import unwrapApolloResult from "../unwrapApolloResult"
import { RuntimeInfo, ExceptionInfo, SentMessage } from "./RuntimeResolver"
import { RuntimeInfo, ExceptionInfo, SentMessage, NotificationInput, Notification, TriggeredNotification } from "./RuntimeResolver"

const PERSPECTIVE_EXPRESSION_FIELDS = `
author
Expand All @@ -17,22 +17,53 @@ data {
proof { valid, invalid, signature, key }
`

const NOTIFICATION_DEFINITION_FIELDS = `
description
appName
appUrl
appIconPath
trigger
perspectiveIds
webhookUrl
webhookAuth
`

const NOTIFICATION_FIELDS = `
id
granted
${NOTIFICATION_DEFINITION_FIELDS}
`

const TRIGGERED_NOTIFICATION_FIELDS = `
notification { ${NOTIFICATION_FIELDS} }
perspectiveId
triggerMatch
`

export type MessageCallback = (message: PerspectiveExpression) => null
export type ExceptionCallback = (info: ExceptionInfo) => null
export type NotificationTriggeredCallback = (notification: TriggeredNotification) => null
export type NotificationRequestedCallback = (notification: Notification) => null

export class RuntimeClient {
#apolloClient: ApolloClient<any>
#messageReceivedCallbacks: MessageCallback[]
#exceptionOccurredCallbacks: ExceptionCallback[]
#notificationTriggeredCallbacks: NotificationTriggeredCallback[]
#notificationRequestedCallbacks: NotificationRequestedCallback[]

constructor(client: ApolloClient<any>, subscribe: boolean = true) {
this.#apolloClient = client
this.#messageReceivedCallbacks = []
this.#exceptionOccurredCallbacks = []
this.#notificationTriggeredCallbacks = []
this.#notificationRequestedCallbacks = []

if(subscribe) {
this.subscribeMessageReceived()
this.subscribeExceptionOccurred()
this.subscribeNotificationTriggered()
this.subscribeNotificationRequested()
}
}

Expand Down Expand Up @@ -238,6 +269,94 @@ export class RuntimeClient {
return runtimeMessageOutbox
}

async requestInstallNotification(notification: NotificationInput) {
const { runtimeRequestInstallNotification } = unwrapApolloResult(await this.#apolloClient.mutate({
mutation: gql`mutation runtimeRequestInstallNotification($notification: NotificationInput!) {
runtimeRequestInstallNotification(notification: $notification)
}`,
variables: { notification }
}))
return runtimeRequestInstallNotification
}

async grantNotification(id: string): Promise<boolean> {
const { runtimeGrantNotification } = unwrapApolloResult(await this.#apolloClient.mutate({
mutation: gql`mutation runtimeGrantNotification($id: String!) {
runtimeGrantNotification(id: $id)
}`,
variables: { id }
}))
return runtimeGrantNotification
}

async notifications(): Promise<Notification[]> {
const { runtimeNotifications } = unwrapApolloResult(await this.#apolloClient.query({
query: gql`query runtimeNotifications {
runtimeNotifications { ${NOTIFICATION_FIELDS} }
}`,
}))
return runtimeNotifications
}

async updateNotification(id: string, notification: NotificationInput): Promise<boolean> {
const { runtimeUpdateNotification } = unwrapApolloResult(await this.#apolloClient.mutate({
mutation: gql`mutation runtimeUpdateNotification($id: String!, $notification: NotificationInput!) {
runtimeUpdateNotification(id: $id, notification: $notification)
}`,
variables: { id, notification }
}))
return runtimeUpdateNotification
}

async removeNotification(id: string): Promise<boolean> {
const { runtimeRemoveNotification } = unwrapApolloResult(await this.#apolloClient.mutate({
mutation: gql`mutation runtimeRemoveNotification($id: String!) {
runtimeRemoveNotification(id: $id)
}`,
variables: { id }
}))
return runtimeRemoveNotification
}


addNotificationTriggeredCallback(cb: NotificationTriggeredCallback) {
this.#notificationTriggeredCallbacks.push(cb)
}

addNotificationRequestedCallback(cb: NotificationRequestedCallback) {
this.#notificationRequestedCallbacks.push(cb)
}

subscribeNotificationTriggered() {
this.#apolloClient.subscribe({
query: gql` subscription {
runtimeNotificationTriggered { ${TRIGGERED_NOTIFICATION_FIELDS} }
}
`}).subscribe({
next: result => {
this.#notificationTriggeredCallbacks.forEach(cb => {
cb(result.data.runtimeNotificationTriggered)
})
},
error: (e) => console.error(e)
})
}

subscribeNotificationRequested() {
this.#apolloClient.subscribe({
query: gql` subscription {
runtimeNotificationRequested { ${NOTIFICATION_FIELDS} }
}
`}).subscribe({
next: result => {
this.#notificationRequestedCallbacks.forEach(cb => {
cb(result.data.runtimeNotificationRequested)
})
},
error: (e) => console.error(e)
})
}

addMessageCallback(cb: MessageCallback) {
this.#messageReceivedCallbacks.push(cb)
}
Expand Down
Loading

0 comments on commit fdc7a83

Please sign in to comment.