Skip to content

Commit

Permalink
Merge branch 'develop' into feature/refactor-wallet-rendered
Browse files Browse the repository at this point in the history
  • Loading branch information
DDDDDanica committed Jun 18, 2024
2 parents dd58323 + f6890a7 commit cb3e0e7
Show file tree
Hide file tree
Showing 18 changed files with 302 additions and 146 deletions.
2 changes: 1 addition & 1 deletion app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 0 additions & 24 deletions app/scripts/app-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,27 +185,3 @@ const registerInPageContentScript = async () => {
};

registerInPageContentScript();

/**
* Creates an offscreen document that can be used to load additional scripts
* and iframes that can communicate with the extension through the chrome
* runtime API. Only one offscreen document may exist, so any iframes required
* by extension can be embedded in the offscreen.html file. See the offscreen
* folder for more details.
*/
async function createOffscreen() {
if (!chrome.offscreen || (await chrome.offscreen.hasDocument())) {
return;
}

await chrome.offscreen.createDocument({
url: './offscreen.html',
reasons: ['IFRAME_SCRIPTING'],
justification:
'Used for Hardware Wallet and Snaps scripts to communicate with the extension.',
});

console.debug('Offscreen iframe loaded');
}

createOffscreen();
127 changes: 109 additions & 18 deletions app/scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,19 @@ import {
shouldEmitDappViewedEvent,
} from './lib/util';
import { generateSkipOnboardingState } from './skip-onboarding';
import { createOffscreen } from './offscreen';

/* eslint-enable import/first */

import { TRIGGER_TYPES } from './controllers/metamask-notifications/constants/notification-schema';

// eslint-disable-next-line @metamask/design-tokens/color-no-hex
const BADGE_COLOR_APPROVAL = '#0376C9';
// eslint-disable-next-line @metamask/design-tokens/color-no-hex
const BADGE_COLOR_NOTIFICATION = '#D73847';
const BADGE_LABEL_APPROVAL = '\u22EF'; // unicode ellipsis
const BADGE_MAX_NOTIFICATION_COUNT = 9;

// Setup global hook for improved Sentry state snapshots during initialization
const inTest = process.env.IN_TEST;
const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore();
Expand Down Expand Up @@ -261,6 +271,8 @@ function saveTimestamp() {
*/
async function initialize() {
try {
const offscreenPromise = isManifestV3 ? createOffscreen() : null;

const initData = await loadStateFromPersistence();

const initState = initData.data;
Expand Down Expand Up @@ -293,6 +305,7 @@ async function initialize() {
{},
isFirstMetaMaskControllerSetup,
initData.meta,
offscreenPromise,
);
if (!isManifestV3) {
await loadPhishingWarningPage();
Expand Down Expand Up @@ -507,13 +520,15 @@ function emitDappViewedMetricEvent(
* @param {object} overrides - object with callbacks that are allowed to override the setup controller logic
* @param isFirstMetaMaskControllerSetup
* @param {object} stateMetadata - Metadata about the initial state and migrations, including the most recent migration version
* @param {Promise<void>} offscreenPromise - A promise that resolves when the offscreen document has finished initialization.
*/
export function setupController(
initState,
initLangCode,
overrides,
isFirstMetaMaskControllerSetup,
stateMetadata,
offscreenPromise,
) {
//
// MetaMask Controller
Expand Down Expand Up @@ -542,6 +557,7 @@ export function setupController(
isFirstMetaMaskControllerSetup,
currentMigrationVersion: stateMetadata.version,
featureFlags: {},
offscreenPromise,
});

setupEnsIpfsResolver({
Expand Down Expand Up @@ -774,45 +790,120 @@ export function setupController(
updateBadge,
);

controller.controllerMessenger.subscribe(
METAMASK_CONTROLLER_EVENTS.METAMASK_NOTIFICATIONS_LIST_UPDATED,
updateBadge,
);

controller.controllerMessenger.subscribe(
METAMASK_CONTROLLER_EVENTS.METAMASK_NOTIFICATIONS_MARK_AS_READ,
updateBadge,
);

controller.controllerMessenger.subscribe(
METAMASK_CONTROLLER_EVENTS.NOTIFICATIONS_STATE_CHANGE,
updateBadge,
);

controller.txController.initApprovals();

/**
* Updates the Web Extension's "badge" number, on the little fox in the toolbar.
* The number reflects the current number of pending transactions or message signatures needing user approval.
*/
function updateBadge() {
const pendingApprovalCount = getPendingApprovalCount();
const unreadNotificationsCount = getUnreadNotificationsCount();

let label = '';
const count = getUnapprovedTransactionCount();
if (count) {
label = String(count);
let badgeColor = BADGE_COLOR_APPROVAL;

if (pendingApprovalCount) {
label = BADGE_LABEL_APPROVAL;
} else if (unreadNotificationsCount > 0) {
label =
unreadNotificationsCount > BADGE_MAX_NOTIFICATION_COUNT
? `${BADGE_MAX_NOTIFICATION_COUNT}+`
: String(unreadNotificationsCount);
badgeColor = BADGE_COLOR_NOTIFICATION;
}
// browserAction has been replaced by action in MV3
if (isManifestV3) {
browser.action.setBadgeText({ text: label });
browser.action.setBadgeBackgroundColor({ color: '#037DD6' });
} else {
browser.browserAction.setBadgeText({ text: label });
browser.browserAction.setBadgeBackgroundColor({ color: '#037DD6' });

try {
const badgeText = { text: label };
const badgeBackgroundColor = { color: badgeColor };

if (isManifestV3) {
browser.action.setBadgeText(badgeText);
browser.action.setBadgeBackgroundColor(badgeBackgroundColor);
} else {
browser.browserAction.setBadgeText(badgeText);
browser.browserAction.setBadgeBackgroundColor(badgeBackgroundColor);
}
} catch (error) {
console.error('Error updating browser badge:', error);
}
}

function getUnapprovedTransactionCount() {
let count =
controller.appStateController.waitingForUnlock.length +
controller.approvalController.getTotalApprovalCount();
function getPendingApprovalCount() {
try {
let pendingApprovalCount =
controller.appStateController.waitingForUnlock.length +
controller.approvalController.getTotalApprovalCount();

if (controller.preferencesController.getUseRequestQueue()) {
pendingApprovalCount +=
controller.queuedRequestController.state.queuedRequestCount;
}
return pendingApprovalCount;
} catch (error) {
console.error('Failed to get pending approval count:', error);
return 0;
}
}

if (controller.preferencesController.getUseRequestQueue()) {
count += controller.queuedRequestController.state.queuedRequestCount;
function getUnreadNotificationsCount() {
try {
const { isMetamaskNotificationsEnabled, isFeatureAnnouncementsEnabled } =
controller.metamaskNotificationsController.state;

const snapNotificationCount = Object.values(
controller.notificationController.state.notifications,
).filter((notification) => notification.readDate === null).length;

const featureAnnouncementCount = isFeatureAnnouncementsEnabled
? controller.metamaskNotificationsController.state.metamaskNotificationsList.filter(
(notification) =>
!notification.isRead &&
notification.type === TRIGGER_TYPES.FEATURES_ANNOUNCEMENT,
).length
: 0;

const walletNotificationCount = isMetamaskNotificationsEnabled
? controller.metamaskNotificationsController.state.metamaskNotificationsList.filter(
(notification) =>
!notification.isRead &&
notification.type !== TRIGGER_TYPES.FEATURES_ANNOUNCEMENT,
).length
: 0;

const unreadNotificationsCount =
snapNotificationCount +
featureAnnouncementCount +
walletNotificationCount;

return unreadNotificationsCount;
} catch (error) {
console.error('Failed to get unread notifications count:', error);
return 0;
}
return count;
}

notificationManager.on(
NOTIFICATION_MANAGER_EVENTS.POPUP_CLOSED,
({ automaticallyClosed }) => {
if (!automaticallyClosed) {
rejectUnapprovedNotifications();
} else if (getUnapprovedTransactionCount() > 0) {
} else if (getPendingApprovalCount() > 0) {
triggerUi();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ export declare type MetamaskNotificationsControllerSelectIsMetamaskNotifications
handler: MetamaskNotificationsController['selectIsMetamaskNotificationsEnabled'];
};

export type MetamaskNotificationsControllerNotificationsListUpdatedEvent = {
type: `${typeof controllerName}:notificationsListUpdated`;
payload: [Notification[]];
};

export type MetamaskNotificationsControllerMarkNotificationsAsRead = {
type: `${typeof controllerName}:markNotificationsAsRead`;
payload: [Notification[]];
};

// Messenger Actions
export type Actions =
| MetamaskNotificationsControllerUpdateMetamaskNotificationsList
Expand Down Expand Up @@ -209,7 +219,9 @@ export type MetamaskNotificationsControllerMessengerEvents =
// Allowed Events
export type AllowedEvents =
| KeyringControllerStateChangeEvent
| PushPlatformNotificationsControllerOnNewNotificationEvent;
| PushPlatformNotificationsControllerOnNewNotificationEvent
| MetamaskNotificationsControllerNotificationsListUpdatedEvent
| MetamaskNotificationsControllerMarkNotificationsAsRead;

// Type for the messenger of MetamaskNotificationsController
export type MetamaskNotificationsControllerMessenger =
Expand Down Expand Up @@ -1004,6 +1016,11 @@ export class MetamaskNotificationsController extends BaseController<
state.metamaskNotificationsList = metamaskNotifications;
});

this.messagingSystem.publish(
`${controllerName}:notificationsListUpdated`,
this.state.metamaskNotificationsList,
);

this.#setIsFetchingMetamaskNotifications(false);
return metamaskNotifications;
} catch (err) {
Expand Down Expand Up @@ -1068,7 +1085,7 @@ export class MetamaskNotificationsController extends BaseController<
log.warn('Something failed when marking notifications as read', err);
}

// Update the state (state is also used on counter & badge)
// Update the state
this.update((state) => {
const currentReadList = state.metamaskNotificationsReadList;
const newReadIds = [...featureAnnouncementNotificationIds];
Expand All @@ -1088,6 +1105,12 @@ export class MetamaskNotificationsController extends BaseController<
},
);
});

// Publish the event
this.messagingSystem.publish(
`${controllerName}:markNotificationsAsRead`,
this.state.metamaskNotificationsList,
);
}

/**
Expand Down Expand Up @@ -1120,6 +1143,10 @@ export class MetamaskNotificationsController extends BaseController<
notification,
...state.metamaskNotificationsList,
];
this.messagingSystem.publish(
`${controllerName}:notificationsListUpdated`,
state.metamaskNotificationsList,
);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,18 +195,21 @@ export async function listenToPushNotifications(
const unsubscribePushNotifications = onBackgroundMessage(
messaging,
async (payload: MessagePayload): Promise<void> => {
const typedPayload = payload;

// if the payload does not contain data, do nothing
try {
const notificationData: NotificationUnion = typedPayload?.data?.data
? JSON.parse(typedPayload?.data?.data)
const data = payload?.data?.data
? JSON.parse(payload?.data?.data)
: undefined;

if (!notificationData) {
// if the payload does not contain data, do nothing
if (!data) {
return;
}

const notificationData = {
...data,
type: data?.type ?? data?.data?.kind,
} as NotificationUnion;

const notification = processNotification(notificationData);
onNewNotification(notification);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import browser from 'webextension-polyfill';

export async function getNotificationImage() {
const iconUrl = await browser.runtime.getURL('../../images/icon-64.png');
return iconUrl;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { t } from '../../../translate';
import type { Notification } from '../../metamask-notifications/types/types';
import ExtensionPlatform from '../../../platforms/extension';
import { getAmount, formatAmount } from './get-notification-data';
import { getNotificationImage } from './get-notification-image';

type PushNotificationMessage = {
title: string;
Expand Down Expand Up @@ -47,9 +48,11 @@ export async function onPushNotification(
return;
}

const iconUrl = await getNotificationImage();

await registration.showNotification(notificationMessage.title, {
body: notificationMessage.description,
icon: './images/icon-64.png',
icon: iconUrl,
tag: notification?.id,
data: notification,
});
Expand Down
11 changes: 11 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,11 @@ export const METAMASK_CONTROLLER_EVENTS = {
// TODO: Add this and similar enums to the `controllers` repo and export them
APPROVAL_STATE_CHANGE: 'ApprovalController:stateChange',
QUEUED_REQUEST_STATE_CHANGE: 'QueuedRequestController:stateChange',
METAMASK_NOTIFICATIONS_LIST_UPDATED:
'MetamaskNotificationsController:notificationsListUpdated',
METAMASK_NOTIFICATIONS_MARK_AS_READ:
'MetamaskNotificationsController:markNotificationsAsRead',
NOTIFICATIONS_STATE_CHANGE: 'NotificationController:stateChange',
};

// stream channels
Expand Down Expand Up @@ -374,6 +379,8 @@ export default class MetamaskController extends EventEmitter {
// the only thing that uses controller connections are open metamask UI instances
this.activeControllerConnections = 0;

this.offscreenPromise = opts.offscreenPromise ?? Promise.resolve();

this.getRequestAccountTabIds = opts.getRequestAccountTabIds;
this.getOpenMetamaskTabsIds = opts.getOpenMetamaskTabsIds;

Expand Down Expand Up @@ -4069,6 +4076,10 @@ export default class MetamaskController extends EventEmitter {
*/
async submitPassword(password) {
const { completedOnboarding } = this.onboardingController.store.getState();

// Before attempting to unlock the keyrings, we need the offscreen to have loaded.
await this.offscreenPromise;

await this.keyringController.submitPassword(password);

///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
Expand Down
Loading

0 comments on commit cb3e0e7

Please sign in to comment.