Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App Notifications through SDNA triggers 1 - base implementation #475

Merged
merged 41 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
66ceb4c
Notifications added to ad4m interface and JS client
lucksus Apr 25, 2024
b5f0fbe
Fix new notification queries
lucksus Apr 25, 2024
deca6b1
Fix new notification queries
lucksus Apr 26, 2024
d8e5dc5
Try without subscriptions
lucksus Apr 26, 2024
d756bba
Fix notification subscription interfaces
lucksus Apr 26, 2024
90a35a1
Basic notification handling test
lucksus Apr 26, 2024
0a298ff
Notifications DB
lucksus Apr 26, 2024
61c635e
CamelCase perspectiveIds
lucksus Apr 26, 2024
7e2e2bd
runtimeNotification query and mutation handlers
lucksus Apr 26, 2024
ae5038e
Notification subscription resolvers
lucksus Apr 26, 2024
d262a9b
Missed file with previous commit
lucksus Apr 29, 2024
6887389
Adjusted notification integration test
lucksus Apr 29, 2024
ced5f33
cargo fix rust-executor
lucksus Apr 29, 2024
5e0dffc
Return notification ID on request_install_notification
lucksus Apr 30, 2024
8bdb70c
Remove console.logs from test and fix assertion
lucksus Apr 30, 2024
5187630
Use sinon for callback mock function
lucksus Apr 30, 2024
4968169
Change appIconPath to be mandatory
lucksus May 3, 2024
a34daa1
Fix new Notification types to have snake_case properties
lucksus May 3, 2024
a96136b
Fix TRIGGERED_NOTIFICATION_FIELDS
lucksus May 14, 2024
697f1cf
Add missing field appIconPath to Notification query fields
lucksus May 14, 2024
320eee6
Fix strings of new pubsub constants
lucksus May 14, 2024
df328c1
Refactor PerspectiveInstance: extract pubsub_publish_diff()
lucksus May 15, 2024
c848bd9
Add missing awaits on extracted pubsub_publish_diff() calls
lucksus May 15, 2024
944bcc8
Refactor PerspectiveInstance: extract spawn_commit_and_handle_error()
lucksus May 15, 2024
4803622
Refactor: use Ad4mDb::with_global_instance in PerspectiveInstance for…
lucksus May 15, 2024
4e49774
Unflake subscription test
lucksus May 15, 2024
9d1c1fc
Warning--
lucksus May 15, 2024
83a49a2
Update Prolog engine async after Perspective change and resend link c…
lucksus May 15, 2024
e021031
Fix agent_is_locked query
lucksus May 16, 2024
98eb5ef
Make tests be ok with multiple link change signals (TODO: add better …
lucksus May 16, 2024
cbebf78
Add missing appIconPath -which is required now- to GQL smoke tests
lucksus May 17, 2024
d8d6329
Fix indentation
lucksus May 17, 2024
15706e3
Integration test for triggering of Notifications
lucksus May 17, 2024
2c3f861
Notification trigger implementation
lucksus May 21, 2024
a459f3f
Remove debugging println!s
lucksus May 21, 2024
107487d
Fix perspective count test - don't assume no other perspectives
lucksus May 21, 2024
76caec7
Sleeps needed in sdna test due to async prolog update
lucksus May 21, 2024
fd1ac1d
Merge branch 'dev' into notifications
lucksus May 21, 2024
9037a7f
feat: Add InstallNotificationRequest to ExceptionType enum
fayeed May 23, 2024
114db53
chore: Remove runtime_notification_requested subscription
fayeed May 23, 2024
f1bef40
refactor: Update runtimeTests to handle InstallNotificationRequest ex…
fayeed May 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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