forked from Koniverse/SubWallet-Extension
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
181 additions
and
37 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,129 @@ | ||
// Copyright 2019-2022 @polkadot/extension authors & contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import type { Message } from '@subwallet/extension-base/types'; | ||
|
||
import { TransportRequestMessage } from '@subwallet/extension-base/background/types'; | ||
import { MESSAGE_ORIGIN_CONTENT, MESSAGE_ORIGIN_PAGE, PORT_CONTENT } from '@subwallet/extension-base/defaults'; | ||
import { Message } from '@subwallet/extension-base/types'; | ||
import { getId } from '@subwallet/extension-base/utils/getId'; | ||
import { addNotificationPopUp } from '@subwallet/extension-koni/helper/PageNotification'; | ||
|
||
// connect to the extension | ||
const port = chrome.runtime.connect({ name: PORT_CONTENT }); | ||
|
||
// redirect users if this page is considered as phishing, otherwise return false | ||
const handleRedirectPhishing: { id: string, resolve?: (value: (boolean | PromiseLike<boolean>)) => void, reject?: (e: Error) => void } = { | ||
id: 'redirect-phishing-' + getId() | ||
}; | ||
|
||
const redirectIfPhishingProm = new Promise<boolean>((resolve, reject) => { | ||
handleRedirectPhishing.resolve = resolve; | ||
handleRedirectPhishing.reject = reject; | ||
function checkForLastError () { | ||
const { lastError } = chrome.runtime; | ||
|
||
if (!lastError) { | ||
return undefined; | ||
} | ||
|
||
// repair incomplete error object (eg chromium v77) | ||
return new Error(lastError.message); | ||
} | ||
|
||
export class ContentHandler { | ||
port?: chrome.runtime.Port; | ||
isShowNotification = false; | ||
isConnected = false; | ||
|
||
const transportRequestMessage: TransportRequestMessage<'pub(phishing.redirectIfDenied)'> = { | ||
id: handleRedirectPhishing.id, | ||
message: 'pub(phishing.redirectIfDenied)', | ||
origin: MESSAGE_ORIGIN_PAGE, | ||
request: null | ||
}; | ||
// Get the port to communicate with the background and init handlers | ||
getPort (): chrome.runtime.Port { | ||
if (!this.port) { | ||
const port = chrome.runtime.connect({ name: PORT_CONTENT }); | ||
const onMessageHandler = this.onPortMessageHandler.bind(this); | ||
|
||
port.postMessage(transportRequestMessage); | ||
}); | ||
const disconnectHandler = () => { | ||
this.onDisconnectPort(port, onMessageHandler, disconnectHandler); | ||
}; | ||
|
||
// send any messages from the extension back to the page | ||
port.onMessage.addListener((data: {id: string, response: any}): void => { | ||
const { id, resolve } = handleRedirectPhishing; | ||
this.port = port; | ||
this.port.onMessage.addListener(onMessageHandler); | ||
this.port.onDisconnect.addListener(disconnectHandler); | ||
} | ||
|
||
if (data?.id === id) { | ||
resolve && resolve(Boolean(data.response)); | ||
} else { | ||
window.postMessage({ ...data, origin: MESSAGE_ORIGIN_CONTENT }, '*'); | ||
return this.port; | ||
} | ||
}); | ||
|
||
// // all messages from the page, pass them to the extension | ||
window.addEventListener('message', ({ data, source }: Message): void => { | ||
// only allow messages from our window, by the inject | ||
if (source !== window || data.origin !== MESSAGE_ORIGIN_PAGE) { | ||
return; | ||
// Handle messages from the background | ||
onPortMessageHandler (data: {id: string, response: any}): void { | ||
const { id, resolve } = handleRedirectPhishing; | ||
|
||
if (data?.id === id) { | ||
resolve && resolve(Boolean(data.response)); | ||
} else { | ||
window.postMessage({ ...data, origin: MESSAGE_ORIGIN_CONTENT }, '*'); | ||
} | ||
} | ||
|
||
port.postMessage(data); | ||
}); | ||
// Handle disconnecting the port from the background | ||
onDisconnectPort (port: chrome.runtime.Port, onMessage: (data: {id: string, response: any}) => void, onDisconnect: () => void): void { | ||
const err = checkForLastError(); | ||
|
||
if (err) { | ||
console.warn(`${err.message}, port is disconnected.`); | ||
} | ||
|
||
port.onMessage.removeListener(onMessage); | ||
port.onDisconnect.removeListener(onDisconnect); | ||
|
||
redirectIfPhishingProm.then((gotRedirected) => { | ||
if (!gotRedirected) { | ||
console.log('Check phishing by URL: Passed.'); | ||
this.port = undefined; | ||
} | ||
}).catch((e) => { | ||
console.warn(`Unable to determine if the site is in the phishing list: ${(e as Error).message}`); | ||
}); | ||
|
||
// Handle messages from the webpage | ||
onPageMessage ({ data, source }: Message): void { | ||
// only allow messages from our window, by the inject | ||
if (source !== window || data.origin !== MESSAGE_ORIGIN_PAGE) { | ||
return; | ||
} | ||
|
||
try { | ||
this.isConnected = true; | ||
this.getPort().postMessage(data); | ||
} catch (e) { | ||
console.error(e); | ||
|
||
if (!this.isShowNotification) { | ||
console.log('The OpenBit extension is not installed. Please install the extension to use the wallet.'); | ||
addNotificationPopUp(); | ||
this.isShowNotification = true; | ||
|
||
setTimeout(() => { | ||
this.isShowNotification = false; | ||
}, 5000); | ||
} | ||
} | ||
} | ||
|
||
// Detect phishing by URL | ||
redirectIfPhishingProm (): void { | ||
new Promise<boolean>((resolve, reject) => { | ||
handleRedirectPhishing.resolve = resolve; | ||
handleRedirectPhishing.reject = reject; | ||
|
||
const transportRequestMessage: TransportRequestMessage<'pub(phishing.redirectIfDenied)'> = { | ||
id: handleRedirectPhishing.id, | ||
message: 'pub(phishing.redirectIfDenied)', | ||
origin: MESSAGE_ORIGIN_PAGE, | ||
request: null | ||
}; | ||
|
||
this.getPort().postMessage(transportRequestMessage); | ||
}).then((gotRedirected) => { | ||
if (!gotRedirected) { | ||
console.log('Check phishing by URL: Passed.'); | ||
} | ||
}).catch((e) => { | ||
console.warn(`Unable to determine if the site is in the phishing list: ${(e as Error).message}`); | ||
}); | ||
} | ||
|
||
constructor () { | ||
this.redirectIfPhishingProm(); | ||
window.addEventListener('message', this.onPageMessage.bind(this)); | ||
} | ||
} | ||
|
||
// @ts-ignore | ||
const contentHandler = new ContentHandler(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Copyright 2019-2022 @polkadot/extension authors & contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
const unableConnectImageSrc = chrome.runtime.getURL('/images/icons/__error__.png'); | ||
|
||
export function removeNotificationPopup () { | ||
const divContainerExisted = document.getElementById('__notification-container'); | ||
|
||
divContainerExisted && divContainerExisted.remove(); | ||
} | ||
|
||
export function addNotificationPopUp () { | ||
removeNotificationPopup(); | ||
|
||
const divContainer = document.createElement('div'); | ||
const divBox = document.createElement('div'); | ||
const imgElement = document.createElement('img'); | ||
const divContent = document.createElement('div'); | ||
const styleElement = document.createElement('style'); | ||
|
||
const notificationContainerStyles: Partial<CSSStyleDeclaration> = { | ||
position: 'fixed', | ||
top: '5%', | ||
zIndex: '10001', | ||
width: '100%', | ||
animation: 'slideDown 5s ease-in-out' | ||
}; | ||
|
||
const notificationBoxStyles: Partial<CSSStyleDeclaration> = { | ||
borderRadius: '8px', | ||
margin: 'auto', | ||
width: 'fit-content', | ||
backgroundColor: 'black', | ||
alignItems: 'center', | ||
border: '2px solid #BF1616', | ||
display: 'flex', | ||
gap: '8px', | ||
padding: '8px 16px 8px 16px' | ||
}; | ||
|
||
const notificationContentStyles: Partial<CSSStyleDeclaration> = { | ||
fontFamily: 'inherit', | ||
fontSize: '14px', | ||
fontStyle: 'normal', | ||
color: 'rgba(255, 255, 255, 0.85)', | ||
fontWeight: '500', | ||
lineHeight: '22px' | ||
}; | ||
|
||
const keyframes = `@keyframes slideDown { | ||
0% { transform: translateY(-100%); opacity: 0; } | ||
20% { transform: translateY(0); opacity: 1; } | ||
95% { transform: translateY(0); opacity: 1; } | ||
100% { transform: translateY(-100%); opacity: 0; } | ||
}`; | ||
|
||
Object.assign(divContent.style, notificationContentStyles); | ||
Object.assign(divContainer.style, notificationContainerStyles); | ||
Object.assign(divBox.style, notificationBoxStyles); | ||
|
||
divContainer.id = '__notification-container'; | ||
imgElement.src = unableConnectImageSrc; | ||
divContent.innerText = 'Unable to connect. Reload dApp site and try again.'; | ||
styleElement.innerHTML = keyframes; | ||
|
||
document.head.appendChild(styleElement); | ||
unableConnectImageSrc !== 'chrome-extension://invalid/' && divBox.appendChild(imgElement); | ||
divBox.appendChild(divContent); | ||
divContainer.appendChild(divBox); | ||
document.body.appendChild(divContainer); | ||
|
||
setTimeout(removeNotificationPopup, 5000); | ||
} |