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

Monitoring XP drop #3679

Merged
merged 17 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 17 additions & 5 deletions background/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ export default class Main extends BaseService<never> {
internalEthereumProviderService,
preferenceService,
)

const notificationsService = NotificationsService.create(preferenceService)

const islandService = IslandService.create(chainService, indexingService)

const telemetryService = TelemetryService.create()
Expand All @@ -353,11 +356,6 @@ export default class Main extends BaseService<never> {
ledgerService,
)

const notificationsService = NotificationsService.create(
preferenceService,
islandService,
)

const walletConnectService = isEnabled(FeatureFlags.SUPPORT_WALLET_CONNECT)
? WalletConnectService.create(
providerBridgeService,
Expand Down Expand Up @@ -669,6 +667,7 @@ export default class Main extends BaseService<never> {
this.connectWalletConnectService()
this.connectAbilitiesService()
this.connectNFTsService()
this.connectNotificationsService()

await this.connectChainService()

Expand Down Expand Up @@ -1592,6 +1591,13 @@ export default class Main extends BaseService<never> {
},
)

this.preferenceService.emitter.on(
"initializeNotificationsPreferences",
async (isPermissionGranted) => {
this.store.dispatch(toggleNotifications(isPermissionGranted))
},
)

this.preferenceService.emitter.on(
"dismissableItemMarkedAsShown",
async (dismissableItem) => {
Expand Down Expand Up @@ -1756,6 +1762,12 @@ export default class Main extends BaseService<never> {
})
}

connectNotificationsService(): void {
this.islandService.emitter.on("newXpDrop", () => {
this.notificationsService.notifyXPDrop()
})
}

async unlockInternalSigners(password: string): Promise<boolean> {
return this.internalSignerService.unlock(password)
}
Expand Down
8 changes: 8 additions & 0 deletions background/services/island/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ interface Events extends ServiceLifecycleEvents {
newEligibility: Eligible
newReferral: { referrer: AddressOnNetwork } & ReferrerStats
monitoringTestnetAsset: SmartContractFungibleAsset
newXpDrop: void
}

/*
Expand Down Expand Up @@ -147,6 +148,9 @@ export default class IslandService extends BaseService<Events> {
)
if (realmXpAsset !== undefined) {
this.emitter.emit("monitoringTestnetAsset", realmXpAsset)
realmContract.on(realmContract.filters.XpDistributed(), () => {
this.checkXPDrop()
})
}
}),
)
Expand Down Expand Up @@ -225,6 +229,10 @@ export default class IslandService extends BaseService<Events> {
return this.db.getReferrerStats(referrer)
}

private checkXPDrop() {
this.emitter.emit("newXpDrop", undefined)
}

private async trackReferrals({
address,
network,
Expand Down
127 changes: 71 additions & 56 deletions background/services/notifications/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { uniqueId } from "lodash"
import browser from "webextension-polyfill"
import BaseService from "../base"
import IslandService from "../island"
import PreferenceService from "../preferences"
import { ServiceCreatorFunction, ServiceLifecycleEvents } from "../types"
import { HOUR } from "../../constants"

const TAHO_ICON_URL =
"https://taho.xyz/icons/icon-144x144.png?v=41306c4d4e6795cdeaecc31bd794f68e"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking, but we should have an extension-local URL available for this I believe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we can do it, also for the wallet-connection-handler (the same approach).


type Events = ServiceLifecycleEvents & {
notificationDisplayed: string
Expand All @@ -11,6 +15,8 @@ type Events = ServiceLifecycleEvents & {

type NotificationClickHandler = (() => Promise<void>) | (() => void)

const NOTIFICATIONS_XP_DROP_THRESHOLD = 24 * HOUR

/**
* The NotificationService manages all notifications for the extension. It is
* charged both with managing the actual notification lifecycle (notification
Expand All @@ -31,21 +37,19 @@ export default class NotificationsService extends BaseService<Events> {
[notificationId: string]: NotificationClickHandler
} = {}

private lastXpDropNotificationInMs?: number

/*
* Create a new NotificationsService. The service isn't initialized until
* startService() is called and resolved.
*/
static create: ServiceCreatorFunction<
Events,
NotificationsService,
[Promise<PreferenceService>, Promise<IslandService>]
> = async (preferenceService, islandService) =>
new this(await preferenceService, await islandService)

private constructor(
private preferenceService: PreferenceService,
private islandService: IslandService,
) {
[Promise<PreferenceService>]
> = async (preferenceService) => new this(await preferenceService)

private constructor(private preferenceService: PreferenceService) {
super()
}

Expand All @@ -63,28 +67,33 @@ export default class NotificationsService extends BaseService<Events> {
// browser notifications permission has been granted. The preferences service
// does guard this, but if that ends up not being true, browser.notifications
// will be undefined and all of this will explode.
this.isPermissionGranted =
await this.preferenceService.getShouldShowNotifications()
ioay marked this conversation as resolved.
Show resolved Hide resolved

this.preferenceService.emitter.on(
"initializeNotificationsPreferences",
async (isPermissionGranted) => {
this.isPermissionGranted = isPermissionGranted
},
)

this.preferenceService.emitter.on(
"setNotificationsPermission",
(isPermissionGranted) => {
if (typeof browser !== "undefined") {
if (isPermissionGranted) {
browser.notifications.onClicked.addListener(
boundHandleNotificationClicks,
)
browser.notifications.onClosed.addListener(
boundCleanUpNotificationClickHandler,
)
} else {
browser.notifications.onClicked.removeListener(
boundHandleNotificationClicks,
)
browser.notifications.onClosed.removeListener(
boundCleanUpNotificationClickHandler,
)
}
this.isPermissionGranted = isPermissionGranted

if (this.isPermissionGranted) {
browser.notifications.onClicked.addListener(
boundHandleNotificationClicks,
)
browser.notifications.onClosed.addListener(
boundCleanUpNotificationClickHandler,
)
} else {
browser.notifications.onClicked.removeListener(
boundHandleNotificationClicks,
)
browser.notifications.onClosed.removeListener(
boundCleanUpNotificationClickHandler,
)
}
},
)
Expand All @@ -95,23 +104,8 @@ export default class NotificationsService extends BaseService<Events> {
boundCleanUpNotificationClickHandler,
)
}

/*
* FIXME add below
this.islandService.emitter.on("xpDropped", this.notifyXpDrop.bind(this))
*/
}

// TODO: uncomment when the XP drop is ready
// protected async notifyDrop(/* xpInfos: XpInfo[] */): Promise<void> {
// const callback = () => {
// browser.tabs.create({
// url: "dapp url for realm claim, XpInfo must include realm id, ideally some way to communicate if the address is right as well",
// })
// }
// this.notify({ callback })
// }

// Fires the click handler for the given notification id.
protected handleNotificationClicks(notificationId: string): void {
this.clickHandlers?.[notificationId]()
Expand All @@ -127,28 +121,49 @@ export default class NotificationsService extends BaseService<Events> {
* The click action, if specified, will be fired when the user clicks on the
* notification.
*/
protected async notify({
title = "",
message = "",
contextMessage = "",
public notify({
options,
callback,
}: {
title?: string
message?: string
contextMessage?: string
options: {
title: string
message: string
contextMessage?: string
type?: browser.Notifications.TemplateType
}
callback?: () => void
}) {
if (!this.isPermissionGranted) {
return
}
const notificationId = uniqueId("notification-")

await browser.notifications.create(notificationId, {
type: "basic",
title,
message,
contextMessage,
isClickable: !!callback,
})
const notificationOptions = {
type: "basic" as browser.Notifications.TemplateType,
iconUrl: TAHO_ICON_URL,
...options,
}
ioay marked this conversation as resolved.
Show resolved Hide resolved

if (typeof callback === "function") {
this.clickHandlers[notificationId] = callback
}

browser.notifications.create(notificationId, notificationOptions)
}

public notifyXPDrop(callback?: () => void): void {
const shouldShowXpDropNotifications = this.lastXpDropNotificationInMs
? Date.now() >
this.lastXpDropNotificationInMs + NOTIFICATIONS_XP_DROP_THRESHOLD
: true

if (shouldShowXpDropNotifications) {
this.lastXpDropNotificationInMs = Date.now()
const options = {
title: "Weekly XP distributed",
message: "Visit Subscape to see if you are eligible",
}
this.notify({ options, callback })
}
}
}
34 changes: 16 additions & 18 deletions background/services/preferences/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import browser from "webextension-polyfill"
import { FiatCurrency } from "../../assets"
import { AddressOnNetwork, NameOnNetwork } from "../../accounts"
import { ServiceLifecycleEvents, ServiceCreatorFunction } from "../types"
Expand Down Expand Up @@ -108,6 +109,7 @@ interface Events extends ServiceLifecycleEvents {
initializeDefaultWallet: boolean
initializeSelectedAccount: AddressOnNetwork
initializeShownDismissableItems: DismissableItem[]
initializeNotificationsPreferences: boolean
updateAnalyticsPreferences: AnalyticsPreferences
addressBookEntryModified: AddressBookEntry
updatedSignerSettings: AccountSignerSettings[]
Expand Down Expand Up @@ -156,6 +158,11 @@ export default class PreferenceService extends BaseService<Events> {
"initializeShownDismissableItems",
await this.getShownDismissableItems(),
)

this.emitter.emit(
"initializeNotificationsPreferences",
await this.getShouldShowNotificationsPreferences(),
)
}

protected override async internalStopService(): Promise<void> {
Expand Down Expand Up @@ -265,32 +272,23 @@ export default class PreferenceService extends BaseService<Events> {
this.emitter.emit("updateAnalyticsPreferences", analytics)
}

async getShouldShowNotifications(): Promise<boolean> {
async getShouldShowNotificationsPreferences(): Promise<boolean> {
return (await this.db.getPreferences()).shouldShowNotifications
}

async setShouldShowNotifications(shouldShowNotifications: boolean) {
const permissionRequest: Promise<boolean> = new Promise((resolve) => {
if (shouldShowNotifications) {
chrome.permissions.request(
{
permissions: ["notifications"],
},
(granted) => {
resolve(granted)
},
)
} else {
resolve(false)
}
})

return permissionRequest.then(async (granted) => {
if (shouldShowNotifications) {
const granted = await browser.permissions.request({
permissions: ["notifications"],
})

await this.db.setShouldShowNotifications(granted)
this.emitter.emit("setNotificationsPermission", granted)

return granted
})
}

return false
}

async getAccountSignerSettings(): Promise<AccountSignerSettings[]> {
Expand Down