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

Refactor/code structure #38

Merged
merged 15 commits into from
Feb 2, 2024
Merged
4,254 changes: 3,809 additions & 445 deletions push-snap-site/pnpm-lock.yaml

Large diffs are not rendered by default.

3,731 changes: 1,498 additions & 2,233 deletions push-snap-site/yarn.lock

Large diffs are not rendered by default.

84 changes: 50 additions & 34 deletions snap-optin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,62 @@ import * as PushAPI from '@pushprotocol/restapi';
import { ENV } from '@pushprotocol/restapi/src/lib/constants';
import axios from 'axios';

const snapOptIn = async (signer:PushAPI.SignerType,address:string, channelAddress: string, chainid: string) => {
/**
* Opt-in a user to a Push Notification channel using Snaps.
* @param signer The signer type used for channel subscription.
* @param address The user's Ethereum address.
* @param channelAddress The address of the channel to opt into.
* @param chainid The ID of the blockchain chain.
*/
const snapOptIn = async (signer: PushAPI.SignerType, address: string, channelAddress: string, chainid: string) => {

const defaultSnapOrigin='npm:@pushprotocol/snap'
// Define the default Snap origin
const defaultSnapOrigin = 'npm:@pushprotocol/snap'

// Request user opt-in using the wallet_invokeSnap method
const res = await window.ethereum?.request({
method: "wallet_invokeSnap",
params: {
snapId: defaultSnapOrigin,
request: { method: "pushproto_optin", params:{
channelAddress: channelAddress
} },
},
method: "wallet_invokeSnap",
params: {
snapId: defaultSnapOrigin,
request: {
method: "pushproto_optin",
params: {
channelAddress: channelAddress
}
},
},
});

// If user opt-in is successful, subscribe the user to the channel
if (res) {
await PushAPI.channels.subscribe({
signer: signer,
channelAddress: `eip155:${chainid}:${channelAddress}`,
userAddress: `eip155:${chainid}:${address}`,
onSuccess: () => {
console.log("opt in success");
},
onError: () => {
console.error("opt in error");
},
env: ENV.PROD,
});

let subscribed = await axios.get(`https://backend-staging.epns.io/apis/v1/users/eip155:${chainid}:${address}/subscriptions`);
subscribed = subscribed.data.subscriptions;
if(subscribed.length == 1){
await window.ethereum?.request({
method: "wallet_invokeSnap",
params: {
snapId: defaultSnapOrigin,
request: { method: "pushproto_firstchanneloptin"},
},
await PushAPI.channels.subscribe({
signer: signer,
channelAddress: `eip155:${chainid}:${channelAddress}`,
userAddress: `eip155:${chainid}:${address}`,
onSuccess: () => {
console.log("opt in success");
},
onError: () => {
console.error("opt in error");
},
env: ENV.PROD,
});
}

// Check if the user has only one subscription and trigger the first channel opt-in
let subscribed = await axios.get(`https://backend-staging.epns.io/apis/v1/users/eip155:${chainid}:${address}/subscriptions`);
subscribed = subscribed.data.subscriptions;
if (subscribed.length == 1) {
await window.ethereum?.request({
method: "wallet_invokeSnap",
params: {
snapId: defaultSnapOrigin,
request: {
method: "pushproto_firstchanneloptin"
},
},
});
}
}
};
};

export default snapOptIn;
export default snapOptIn;
2 changes: 1 addition & 1 deletion snap-ui/src/components/SnapButton/SnapOptInButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const SnapOptInButton = ({ address, signer }: Props) => {
}, [signer, address]);

const checkSubscription = async () => {
const url = `https://backend-staging.epns.io/apis/v1/channels/eip155:5:${address}/subscribers`;
const url = `https://backend-staging.epns.io/apis/v1/channels/eip155:11155111:${address}/subscribers`;

let response = await fetch(url, {
method: "get",
Expand Down
15 changes: 13 additions & 2 deletions snap/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
module.exports = {
extends: ['../../.eslintrc.js'],

env: {
browser: true,
es2021: true,
},
extends: ['plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
rules: {
},
ignorePatterns: ['!.eslintrc.js', 'dist/'],
};
3 changes: 3 additions & 0 deletions snap/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
/node_modules

# Optional eslint cache
.eslintcache
2 changes: 1 addition & 1 deletion snap/dist/bundle.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions snap/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pushprotocol/snap",
"version": "1.1.11",
"version": "1.1.12",
"description": "Get Push Notifications directly in your MetaMask wallet!",
"repository": {
"type": "git",
Expand All @@ -23,7 +23,7 @@
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write",
"lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' --ignore-path .gitignore",
"serve": "mm-snap serve",
"start": "mm-snap watch"
"start": "tsc --noEmit && mm-snap watch"
},
"devDependencies": {
"@lavamoat/allow-scripts": "^2.0.3",
Expand All @@ -32,7 +32,7 @@
"@metamask/eslint-config-jest": "^10.0.0",
"@metamask/eslint-config-nodejs": "^10.0.0",
"@metamask/eslint-config-typescript": "^10.0.0",
"@metamask/snaps-cli": "^4.0.0",
"@metamask/snaps-cli": "^4.0.1",
"@metamask/snaps-types": "^0.32.2",
"@metamask/snaps-ui": "^0.32.2",
"@typescript-eslint/eslint-plugin": "^5.33.0",
Expand Down
18 changes: 3 additions & 15 deletions snap/snap.manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"version": "1.1.11",
"version": "1.1.12",
"description": "Get Push Notifications directly in your MetaMask wallet!",
"proposedName": "Push",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-push-notification-service/push-protocol-snaps"
},
"source": {
"shasum": "MYvU7J3wZXZAG+NFY7R+F6yUh1m0Liaum64Ie97uxxo=",
"shasum": "3h8QmRFISxpD5m+g41R5ZvNlMFMSe14ITrjkVnLwI9M=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand All @@ -29,19 +29,7 @@
{
"expression": "* * * * *",
"request": {
"method": "fireCronjob"
}
},
{
"expression": "0 1 * * *",
"request": {
"method": "checkActivity"
}
},
{
"expression": "* * * * *",
"request": {
"method": "pushproto_removesnooze"
"method": "notifCronJob"
}
}
]
Expand Down
15 changes: 15 additions & 0 deletions snap/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { LatestSnapState } from "../types";

export const allowedSnapOrigins = [
"https://app.push.org",
"https://staging.push.org",
"https://dev.push.org",
"http://localhost:3000",
];

export const BASE_URL = 'https://backend.epns.io/apis/v1'; // Modify this as needed

export const defaultLatestSnapState: LatestSnapState = {
version: 1,
addresses: {}
}
33 changes: 33 additions & 0 deletions snap/src/handlers/cronJobHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { OnCronjobHandler } from "@metamask/snaps-types";
import { notifCronJob } from "../methods";
import { SnapCronJobMethod } from "../types";

/**
* Handles cronjobs for the Snap, executing the appropriate method based on the request.
* @param {object} options - The options for handling the cronjob.
* @param {object} options.request - The request object containing information about the cronjob.
* @param {string} options.request.method - The method to execute for the cronjob.
* @throws {Error} Throws an error if the specified method is not found.
*/
export const onCronjob: OnCronjobHandler = async ({ request }) => {
try {
switch (request.method as SnapCronJobMethod) {
case SnapCronJobMethod.NotifCronJob:
await notifCronJob();
break;
// case SnapCronJobMethod.CheckActivityCronJob:
// await checkActivityCronJob();
// break;
// case SnapCronJobMethod.RemoveSnoozeCronJob:
// await removeSnoozeCronJob();
// break;
default:
throw new Error("Method not found.");
}
} catch (error) {
// Handle or log the error as needed
console.error("Error in onCronjob:", error);
// Optionally rethrow the error if you want it to propagate further
throw error;
}
};
2 changes: 2 additions & 0 deletions snap/src/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { onRpcRequest } from "./rpcRequestHandler";
export { onCronjob } from "./cronJobHandler";
133 changes: 133 additions & 0 deletions snap/src/handlers/rpcRequestHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { OnRpcRequestHandler } from "@metamask/snaps-types";
import { divider, heading, panel, text } from "@metamask/snaps-ui";
import { ApiParams, ApiRequestParams, SnapRpcMethod } from "../types";
import {
addAddress,
channelOptin,
removeAddress,
welcomeDialog,
} from "../methods";
import { getEnabledAddresses, getModifiedSnapState } from "../utils";
import { allowedSnapOrigins } from "../config";

/**
* Handles RPC requests from Snap-enabled dapps.
* @param {Object} params - The parameters object containing the origin and request.
* @param {string} params.origin - The origin of the request.
* @param {Object} params.request - The request object containing the method and parameters.
* @returns {Promise<any>} - The result of the RPC request.
*/
export const onRpcRequest: OnRpcRequestHandler = async ({
origin,
request,
}) => {
try {
// Check if the origin is allowed
if (allowedSnapOrigins.includes(origin)) {
const requestParams = request?.params as unknown as ApiRequestParams;

// For non-encrypted state
// ToDo: For encrypted state, when its use case comes

// Retrieve the current Snap state and modify it to the latest version if necessary
const state = await getModifiedSnapState({ encrypted: false });

const apiParams: ApiParams = {
state,
requestParams,
};

// Handles different RPC methods
switch (request.method as SnapRpcMethod) {
case SnapRpcMethod.AddAddress: {
// Handles the addAddress RPC method
return addAddress(apiParams);
}
case SnapRpcMethod.RemoveAddress: {
// Handles the removeAddress RPC method
return removeAddress(apiParams);
}
case SnapRpcMethod.Welcome: {
// Handles the welcome RPC method
return welcomeDialog();
}
// case SnapRpcMethod.TogglePopup: {
// // Handles the togglePopup RPC method
// return togglePopup(apiParams);
// }
// case SnapRpcMethod.SnoozeDuration: {
// await snoozeDuration();
// break;
// }
case SnapRpcMethod.OptIn: {
// Handles the optIn RPC method
return channelOptin(apiParams);
}
case SnapRpcMethod.OptInComplete: {
// Displays a success message for OptInComplete RPC method
await snap.request({
method: "snap_dialog",
params: {
type: "alert",
content: panel([
heading("Channel Opt-In"),
divider(),
text(
`You've successfully opted into the channel to receive notifications directly into MetaMask`
),
]),
},
});
break;
}
case SnapRpcMethod.GetAddresses: {
const addresses = getEnabledAddresses(state);
return addresses;
}
// case SnapRpcMethod.GetToggleStatus: {
// // Retrieve and return the toggle status from Snap storage
// const persistedData = await SnapStorageCheck();
// const popuptoggle = persistedData.popuptoggle;
// return popuptoggle;
// }
case SnapRpcMethod.FirstChannelOptIn: {
// Displays a congratulations message for FirstChannelOptIn RPC method
await snap.request({
method: "snap_dialog",
params: {
type: "alert",
content: panel([
heading("Congratulations!"),
divider(),
text(`You have successfully opted in to your first channel. \n\n
Now, you are all set to receive notifications directly to your MetaMask Wallet.`),
]),
},
});
break;
}
default:
// Throw an error for unsupported RPC methods
throw new Error("Method not found.");
}
} else {
// Display an error message if the dapp is not supported
await snap.request({
method: "snap_dialog",
params: {
type: "alert",
content: panel([
heading("Error"),
text("This dapp is not supported by Push Notification Snap"),
]),
},
});
return true;
}
} catch (error) {
// Handle or log the error as needed
console.error("Error in onRpcRequest:", error);
// Optionally rethrow the error if you want it to propagate further
throw error;
}
};
Loading