Slider Notification Settings in Showrunners
This example is intended to get you understand slider based notification settings with a real-world application. For the example we are going to look at a scenario where users can choose a time interval and showrunners framework will notify them as per their request. Checkout Showrunners Docs, Showrunners Framework, Channel Settings Docs and Channel Settings Demo for better understanding!
What we gonna build?
Imagine you are a crypto trader or a general crypto enthusiast. You want to be notified every once in a while about the price movements and activities in the market. But you either lose track of time or forget about it. To solve this exact problem, we will be looking into a slider type notification settings implementation where you as a user can specify the time interval after which you would like to get notified.
We will choose the Eth Tracker channel to demonstrate this example.
Creating Eth Tracker in Showrunners
Step 1: Setup the Showrunners in your local machine
For detailed, step-by-step guide visit the Showrunners docs. First we need to create a folder in src/showrunners/<your_channel_name>
Step 2: Install Dependencies & start up
Navigate to the SDK directory and install required dependencies.
cd push-showrunners-framework
yarn install
docker-compose up
yarn run dev
Step 3: Import the Push SDK
After you have created a channel folder. Refer to Showrunners docs. Move to the [name]Channel.ts file and import the dependencies.
import { PushAPI } from '@pushprotocol/restapi';
Channel File
In order to send notification, we need to have the instance of the user channel. To get that, we need to add the below function in our 'Channel class' .
const provider = new ethers.providers.JsonRpcProvider(settings.providerUrl);
const signer = new ethers.Wallet(keys.PRIVATE_KEY_NEW_STANDARD.PK, provider);
const userAlice = await PushAPI.initialize(signer, {
env: CONSTANTS.ENV.STAGING,
});
Here, you can use any provider of your choice and fetch the signer using the private key of the wallet that was used to create the channel. The userAlice
is an instance of the channel using the PushAPI
from the sdk. This will allow us to fetch data of subscribers and their notification settings to build our logic around.
Let's get to building it.
Fetch subscribers data
To fetch a list of all users who have opted into receiving notifications along with their opted value from userAlice
, you can use the subscribers
method. You can read about this method in detail here.
const userData: any = await userAlice.channel.subscribers({
page: i,
limit: 10,
setting: true,
});
// Output :
{
"itemcount": 5,
"subscribers": [
{
"subscriber": "0x279c00e16c638a752ea42ae5e09db3c3992f70ad",
"settings": "[{\"type\": 2, \"user\": 8, \"index\": 1, \"ticker\": 1, \"default\": 1, \"enabled\": true, \"lowerLimit\": 1, \"upperLimit\": 10, \"description\": \"Price Range\"}]"
},
{
"subscriber": "0x49403ae592c82fc3f861cd0b9738f7524fb1f38c",
"settings": "[{\"type\": 2, \"user\": 1, \"index\": 1, \"ticker\": 1, \"default\": 1, \"enabled\": true, \"lowerLimit\": 1, \"upperLimit\": 10, \"description\": \"Price Range\"}]"
},
{
"subscriber": "0x71ffa5771e8019787190d098586efe02026a3c8c",
"settings": "[{\"type\": 2, \"user\": 2, \"index\": 1, \"ticker\": 1, \"default\": 1, \"enabled\": true, \"lowerLimit\": 1, \"upperLimit\": 10, \"description\": \"Price Range\"}]"
},
{
"subscriber": "0x7a45f2e84055b0c79696c9533c97a4b21dee30d3",
"settings": "[{\"type\": 2, \"user\": 2, \"index\": 1, \"ticker\": 1, \"default\": 1, \"enabled\": true, \"lowerLimit\": 1, \"upperLimit\": 10, \"description\": \"Price Range\"}]"
},
{
"subscriber": "0xc1836ce1eb918cfc8e9acab71ce9c6e1ebe0dff0",
"settings": "[{\"type\": 2, \"user\": 7, \"index\": 1, \"ticker\": 1, \"default\": 1, \"enabled\": true, \"lowerLimit\": 1, \"upperLimit\": 10, \"description\": \"Price Range\"}]"
}
]
}
The main parameters that we need for our use-case is type
, user
, index
and enabled
. Let's go one by one in brief.
type
- Type here tells us if a notification type is boolean or slider type. There are:1 ---> boolean
and2 ---> slider
.user
- This parameter tells us thevalue a specific user
chose in their settings. A value in case of a slider and true/false in case of boolean.index
- This tells us the index of the notification settings as per their creation by the channel owner. The first channel settings gets the index value 1, the next gets 2 and so on.enabled
- As the name suggests, it tells us if an user haveopted in for a settings or not
. It's values can be true or false.
Basic logic implementation
let i = 1;
while (true) {
const userData: any = await userAlice.channel.subscribers({
page: i,
limit: 10,
setting: true,
});
if (userData.itemcount != 0) {
i++;
} else {
console.log("Breakkkk.")
i=1;
break;
}
// Loop through the `settings` array for the required type (say 2 here) --> Time interval
userData.subscribers.map(async (subscriberObj) => {
const userSettings = JSON.parse(subscriberObj.settin
const mappedValue = await ethTickerModel.findOne({ subscriber: subscriberObj.subscriber });
if (userSettings !== null) {
const temp = userSettings.find((obj) => obj.index == 2); // for time interval
let userValue: number;
//IF user has enabled notification then enter
if (temp.enabled === true) {
userValue = temp.userValue;
if (mappedValue.lastCycle + userValue == CYCLES) {
recipients.push(subscriberObj.subscriber);
// UPDATE the users mapped value in DB
await ethTickerModel.findOneAndUpdate(
{ subscriber: subscriberObj.subscriber },
{ lastCycle: mappedValue.lastCycle + userValue},
{ upsert: true },
);
}
}
});
}
Let's go step-by-step here.
- We run a loop through all the subscribers of a channel. Remember to handle pagination as the subscribers' list is paginated.
- Here, we can consider a global variable
CYCLES
that we store in our database that is responsible to track the iterations of the cron-job we setup for 1 hour. - We check if a new subscriber is added to the channel at the start of every cron-job, if yes then we add
subscriber address
mapped to the currentCYCLES
value in MongoDB. - For sending notification, if the
mappedValue + userValue == CYCLES
, we send a notification. - We then update the value in DB,
mappedValue += userValue
- After the entire logic is done, we update the CYCLES variable,
CYCLES++
This is the basic logic behind the notification trigger.
Build the notification payload
Design your own payload with custom values in the when you want to trigger notifications. To learn more about notification settings, refer to docs
const payload = {
notification: {
title: 'Title',
body: 'Notif Body',
},
payload: {
title: 'Title',
body: 'Payload body',
cta: 'https://google.com/',
embed: 'https://avatars.githubusercontent.com/u/64157541?s=200&v=4',
// index of the notification the channel wants to trigger, in this for 1nd index which is for Boolean type
category: 1, // Depending upon your use-case
},
};
Setup Notification trigger
public async sendMessageToNode(simulate) {
const logger = this.logger;
this.getNewPrice()
.then(async (payload: any) => {
for (let i = 0; i < payload.recipients.length; i++) {
this.sendNotification({
recipient: payload.recipients[i], // new
title: payload.notifTitle,
message: payload.notifMsg,
payloadTitle: payload.title,
payloadMsg: payload.msg,
notificationType: payload.type,
simulate: simulate,
image: null,
});
}
})
.catch(err => {
logger.error(`[${new Date(Date.now())}]-[ETH Ticker]- Errored on CMC API... skipped with error: %o`, err);
});
}
How does it actually work?
Summarizing what we implemented in the example, we use a concept of a global clock (say CYCLES) using a variable that is stored in our database and it gets updated after every iteration of the cron-job that runs for every 1 hour. We check if any new users have subscribed to the channel after the last iteration and store a mapping of the user address and the current CYCLES value. This mapping will allow us to calculate the correct dispatch time of notification as per user.
Well, that's it. You now have a clear understanding of how notification slider settings work and you can use it to build amazing use-cases. We'll see you in another one. Until then keep building🔥