diff --git a/packages/restapi/src/lib/channels/index.ts b/packages/restapi/src/lib/channels/index.ts index 8728fc02a..75f186399 100644 --- a/packages/restapi/src/lib/channels/index.ts +++ b/packages/restapi/src/lib/channels/index.ts @@ -9,4 +9,6 @@ export * from './subscribe'; export * from './subscribeV2'; export * from './unsubscribe'; export * from './unsubscribeV2'; +export {subscribeV3} from "./subscribeV3"; +export {unsubscribeV3} from "./unsubscribeV3"; diff --git a/packages/restapi/src/lib/channels/signature.helpers.ts b/packages/restapi/src/lib/channels/signature.helpers.ts index fadb4e480..e6513767a 100644 --- a/packages/restapi/src/lib/channels/signature.helpers.ts +++ b/packages/restapi/src/lib/channels/signature.helpers.ts @@ -50,6 +50,32 @@ export const getSubscriptionMessageV2 = ( } }; +export const getSubscriptionMessageV3 = ( + channel: string, + userAddress: string, + action: channelActionType, + userSetting?: string | null +) => { + const actionTypeKey = + action === 'Unsubscribe' ? 'unsubscriber' : 'subscriber'; + if (action == 'Subscribe') { + return JSON.stringify({ + channel, + [actionTypeKey]: userAddress, + action: action, + userSetting: userSetting?? '', + timestamp: Math.floor(Date.now() / 1000), + }, null, 4); + } else { + return JSON.stringify({ + channel, + [actionTypeKey]: userAddress, + action: action, + timestamp: Math.floor(Date.now() / 1000), + }, null, 4); + } +}; + export const getTypeInformation = (action: string) => { if (action === 'Subscribe') { return { diff --git a/packages/restapi/src/lib/channels/subscribeV3.ts b/packages/restapi/src/lib/channels/subscribeV3.ts new file mode 100644 index 000000000..1b545a27d --- /dev/null +++ b/packages/restapi/src/lib/channels/subscribeV3.ts @@ -0,0 +1,106 @@ +import { getCAIPAddress, getConfig, getCAIPDetails, Signer } from '../helpers'; +import { + getDomainInformation, + getTypeInformationV2, + getSubscriptionMessageV3, +} from './signature.helpers'; +import Constants, { ENV } from '../constants'; +import { SignerType } from '../types'; +import { axiosPost } from '../utils/axiosUtil'; + +export type SubscribeOptionsV2Type = { + signer: SignerType; + channelAddress: string; + userAddress: string; + settings?: string | null; + verifyingContractAddress?: string; + env?: ENV; + origin?: string; + onSuccess?: () => void; + onError?: (err: Error) => void; +}; + +export const subscribeV3 = async (options: SubscribeOptionsV2Type) => { + const { + signer, + channelAddress, + userAddress, + settings = undefined, + verifyingContractAddress, + env = Constants.ENV.PROD, + origin, + onSuccess, + onError, + } = options || {}; + try { + const _channelAddress = await getCAIPAddress( + env, + channelAddress, + 'Channel' + ); + + const channelCAIPDetails = getCAIPDetails(_channelAddress); + if (!channelCAIPDetails) throw Error('Invalid Channel CAIP!'); + + const chainId = parseInt(channelCAIPDetails.networkId, 10); + + const _userAddress = await getCAIPAddress(env, userAddress, 'User'); + + const userCAIPDetails = getCAIPDetails(_userAddress); + if (!userCAIPDetails) throw Error('Invalid User CAIP!'); + + const { API_BASE_URL, EPNS_COMMUNICATOR_CONTRACT } = getConfig( + env, + channelCAIPDetails + ); + + const requestUrl = `${API_BASE_URL}/v1/channels/${_channelAddress}/subscribe`; + // get domain information + const domainInformation = getDomainInformation( + chainId, + verifyingContractAddress || EPNS_COMMUNICATOR_CONTRACT + ); + + // get type information + const typeInformation = getTypeInformationV2(); + + // get message + const messageInformation = { + data: getSubscriptionMessageV3( + channelCAIPDetails.address, + userCAIPDetails.address, + 'Subscribe', + settings + ), + }; + // sign a message using EIP712 + const pushSigner = new Signer(signer); + const signature = await pushSigner.signTypedData( + domainInformation, + typeInformation, + messageInformation, + 'Data' + ); + + const verificationProof = signature; // might change + + const body = { + verificationProof: `eip712v3:${verificationProof}`, + message: messageInformation.data, + origin: origin + }; + + const res = await axiosPost(requestUrl, body); + + if (typeof onSuccess === 'function') onSuccess(); + + return { status: res.status, message: 'successfully opted into channel' }; + } catch (err: any) { + if (typeof onError === 'function') onError(err as Error); + + return { + status: err?.response?.status ?? '', + message: err instanceof Error ? err.message : JSON.stringify(err), + }; + } +}; diff --git a/packages/restapi/src/lib/channels/unsubscribeV3.ts b/packages/restapi/src/lib/channels/unsubscribeV3.ts new file mode 100644 index 000000000..f0c639ee9 --- /dev/null +++ b/packages/restapi/src/lib/channels/unsubscribeV3.ts @@ -0,0 +1,103 @@ +import { getCAIPAddress, getConfig, getCAIPDetails, Signer } from '../helpers'; +import { + getDomainInformation, + getTypeInformationV2, + getSubscriptionMessageV3, +} from './signature.helpers'; +import Constants, { ENV } from '../constants'; +import { SignerType } from '../types'; +import { axiosPost } from '../utils/axiosUtil'; + +export type UnSubscribeOptionsV2Type = { + signer: SignerType; + channelAddress: string; + userAddress: string; + verifyingContractAddress?: string; + env?: ENV; + onSuccess?: () => void; + onError?: (err: Error) => void; +}; + +export const unsubscribeV3 = async (options: UnSubscribeOptionsV2Type) => { + const { + signer, + channelAddress, + userAddress, + verifyingContractAddress, + env = Constants.ENV.PROD, + onSuccess, + onError, + } = options || {}; + + try { + const _channelAddress = await getCAIPAddress( + env, + channelAddress, + 'Channel' + ); + + const channelCAIPDetails = getCAIPDetails(_channelAddress); + if (!channelCAIPDetails) throw Error('Invalid Channel CAIP!'); + + const chainId = parseInt(channelCAIPDetails.networkId, 10); + + const _userAddress = await getCAIPAddress(env, userAddress, 'User'); + + const userCAIPDetails = getCAIPDetails(_userAddress); + if (!userCAIPDetails) throw Error('Invalid User CAIP!'); + + const { API_BASE_URL, EPNS_COMMUNICATOR_CONTRACT } = getConfig( + env, + channelCAIPDetails + ); + + const requestUrl = `${API_BASE_URL}/v1/channels/${_channelAddress}/unsubscribe`; + + // get domain information + const domainInformation = getDomainInformation( + chainId, + verifyingContractAddress || EPNS_COMMUNICATOR_CONTRACT + ); + + // get type information + const typeInformation = getTypeInformationV2(); + + // get message + const messageInformation = { + data: getSubscriptionMessageV3( + channelCAIPDetails.address, + userCAIPDetails.address, + 'Unsubscribe' + ), + }; + + // sign a message using EIP712 + const pushSigner = new Signer(signer); + const signature = await pushSigner.signTypedData( + domainInformation, + typeInformation, + messageInformation, + 'Data' + ); + + const verificationProof = signature; // might change + + const body = { + verificationProof: `eip712v3:${verificationProof}`, + message: messageInformation.data, + }; + + const res = await axiosPost(requestUrl, body); + + if (typeof onSuccess === 'function') onSuccess(); + + return { status: res.status, message: 'successfully opted out channel' }; + } catch (err: any) { + if (typeof onError === 'function') onError(err as Error); + + return { + status: err?.response?.status ?? '', + message: err instanceof Error ? err.message : JSON.stringify(err), + }; + } +}; diff --git a/packages/restapi/src/lib/pushNotification/notification.ts b/packages/restapi/src/lib/pushNotification/notification.ts index 896ba3ee7..bffaf6245 100644 --- a/packages/restapi/src/lib/pushNotification/notification.ts +++ b/packages/restapi/src/lib/pushNotification/notification.ts @@ -167,7 +167,7 @@ export class Notification extends PushNotificationBaseClass { ); // convert the setting to minimal version const minimalSetting = this.getMinimalUserSetting(settings!); - return await PUSH_CHANNEL.subscribeV2({ + return await PUSH_CHANNEL.subscribeV3({ signer: this.signer!, channelAddress: channel, userAddress: userAddressInCaip, @@ -218,7 +218,7 @@ export class Notification extends PushNotificationBaseClass { this.account!, parseInt(caipDetail?.networkId as string) ); - return await PUSH_CHANNEL.unsubscribeV2({ + return await PUSH_CHANNEL.unsubscribeV3({ signer: this.signer!, channelAddress: channel, userAddress: userAddressInCaip,