Skip to content

Commit

Permalink
heavily refactor ipc from remote hackary so everything works again
Browse files Browse the repository at this point in the history
a smaller change is probably waranted but i am lazy.

closes #352
closes #351
closes #350
closes #347
closes #345
  • Loading branch information
OrangeDrangon committed Dec 28, 2021
1 parent 68a6e58 commit bbcc621
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 370 deletions.
80 changes: 42 additions & 38 deletions src/background.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { app, Event as ElectronEvent, Menu, shell } from "electron";
import { app, Event as ElectronEvent, ipcMain, Menu, shell } from "electron";
import { BrowserWindow } from "electron/main";
import path from "path";
import process from "process";
import { checkForUpdate } from "./helpers/autoUpdate";
import {
IS_DEV,
Expand All @@ -8,9 +10,9 @@ import {
IS_WINDOWS,
RESOURCES_PATH,
} from "./helpers/constants";
import { MenuManager } from "./helpers/menuManager";
import { settings } from "./helpers/settings";
import { TrayManager } from "./helpers/trayManager";
import { CustomBrowserWindow } from "./helpers/window";
import { Conversation, TrayManager } from "./helpers/trayManager";
import { baseMenuTemplate } from "./menu/baseMenu";
import { popupContextMenu } from "./menu/contextMenu";
import { devMenuTemplate } from "./menu/devMenu";
Expand All @@ -25,7 +27,7 @@ const {
checkForUpdateOnLaunchEnabled,
} = settings;

let mainWindow: CustomBrowserWindow;
let mainWindow: BrowserWindow;
let trayManager: TrayManager;

app.on("second-instance", () => {
Expand All @@ -40,25 +42,6 @@ if (!app.requestSingleInstanceLock()) {
app.quit();
}

const setApplicationMenu = () => {
const menus = baseMenuTemplate;
if (IS_DEV) {
menus.push(devMenuTemplate);
}
menus.push(helpMenuTemplate);
Menu.setApplicationMenu(Menu.buildFromTemplate(menus));
};

/**
* Save userData in separate folders for each environment.
* Thanks to this you can use production and development versions of the app
* on same machine like those are two separate apps.
*/
if (IS_DEV) {
const userDataPath = app.getPath("userData");
app.setPath("userData", `${userDataPath}-(${process.env.NODE_ENV})`);
}

if (IS_WINDOWS) {
app.setAppUserModelId("pw.kmr.android-messages-desktop");
app.setAsDefaultProtocolClient("android-messages-desktop");
Expand All @@ -67,13 +50,7 @@ if (IS_WINDOWS) {
app.on("ready", () => {
trayManager = new TrayManager();

setApplicationMenu();

if (IS_MAC) {
app.on("activate", () => {
mainWindow.show();
});
}
new MenuManager();

if (checkForUpdateOnLaunchEnabled.value && !IS_DEV) {
checkForUpdate(true);
Expand All @@ -82,24 +59,31 @@ app.on("ready", () => {
const { width, height } = savedWindowSize.value;
const { x, y } = savedWindowPosition.value ?? {};

mainWindow = new CustomBrowserWindow("main", {
mainWindow = new BrowserWindow({
width,
height,
x,
y,
autoHideMenuBar: autoHideMenuEnabled.value,
title: "Android Messages",
show: false, //don't show window just yet (issue #229)
icon: IS_LINUX
? path.resolve(RESOURCES_PATH, "icons", "128x128.png")
: undefined,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
preload: path.resolve(app.getAppPath(), "bridge.js"),
preload: IS_DEV
? path.resolve(app.getAppPath(), "bridge.js")
: path.resolve(app.getAppPath(), "app", "bridge.js"),
},
});

process.env.MAIN_WINDOW_ID = mainWindow.id.toString();

if (!(settings.trayEnabled.value && settings.startInTrayEnabled.value)) {
mainWindow.show();
}
// set user agent to potentially make google fi work
const userAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0";
Expand All @@ -114,14 +98,12 @@ app.on("ready", () => {
})
);

// Quick and dirty way for renderer process to access mainWindow for communication
app.mainWindow = mainWindow;
app.trayManager = trayManager;
app.settings = settings;

mainWindow.loadURL("https://messages.google.com/web/");

trayManager.startIfEnabled();
settings.showIconsInRecentConversationTrayEnabled.subscribe(() =>
trayManager.refreshTrayMenu()
);

let quitViaContext = false;
app.on("before-quit", () => {
Expand Down Expand Up @@ -173,3 +155,25 @@ app.on("ready", () => {
}
);
}); //onready

ipcMain.on("should-hide-notification-content", (event) => {
event.returnValue = settings.hideNotificationContentEnabled.value;
});

ipcMain.on("show-main-window", (event) => {
mainWindow.show();
});

ipcMain.on("flash-main-window-if-not-focused", (event) => {
if (!mainWindow.isFocused()) {
mainWindow.flashFrame(true);
}
});

ipcMain.on("set-unread-status", (event, unreadStatus: boolean) => {
trayManager.setUnread(unreadStatus);
});

ipcMain.on("set-recent-conversations", (event, data: Conversation[]) => {
trayManager.setRecentConversations(data);
});
176 changes: 33 additions & 143 deletions src/bridge.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,19 @@
import { remote, NotificationConstructorOptions } from "electron";
import { ipcRenderer } from "electron";
import path from "path";
import { INITIAL_ICON_IMAGE, RESOURCES_PATH } from "./helpers/constants";
import {
INITIAL_ICON_IMAGE,
IS_DEV,
RECENT_CONVERSATION_TRAY_COUNT,
RESOURCES_PATH,
} from "./helpers/constants";
createRecentThreadObserver,
createUnreadObserver,
focusFunctions,
recentThreadObserver,
} from "./helpers/observers";
import { getProfileImg } from "./helpers/profileImage";
import { popupContextMenu } from "./menu/contextMenu";

const { Notification: ElectronNotification, app, dialog } = remote;

function unreadObserver() {
if (document.querySelector(".unread") != null) {
app.trayManager?.setUnread(true);
} else {
app.trayManager?.setUnread(false);
}
}

function createUnreadObserver() {
const observer = new MutationObserver(unreadObserver);
observer.observe(
(document.body.querySelector(
"mws-conversations-list"
) as unknown) as Element,
{
subtree: true,
attributes: true,
attributeFilter: ["data-e2e-is-unread"],
}
);
return observer;
}

function recentThreadObserver() {
const conversations = Array.from(
document.body.querySelectorAll("mws-conversation-list-item")
).slice(0, RECENT_CONVERSATION_TRAY_COUNT);

const data = conversations.map((conversation) => {
const name = conversation.querySelector("a div.text-content h3.name span")
?.textContent;
const canvas = conversation.querySelector(
"a div.avatar-container canvas"
) as HTMLCanvasElement | null;

const image = canvas?.toDataURL();

const recentMessage = conversation.querySelector(
"a div.text-content div.snippet-text mws-conversation-snippet span"
)?.textContent;

const click = () => void conversation.querySelector("a")?.click();

return { name, image, recentMessage, click };
});
app.trayManager?.setRecentConversations(data);
}

function createRecentThreadObserver() {
const observer = new MutationObserver(recentThreadObserver);
observer.observe(
(document.body.querySelector(
"mws-conversations-list"
) as unknown) as Element,
{
attributes: false,
subtree: true,
childList: true,
}
);
return observer;
}

window.addEventListener("load", () => {
const conversationListObserver = new MutationObserver(() => {
if (document.querySelector("mws-conversations-list") != null) {
createUnreadObserver();
createRecentThreadObserver();
app.settings?.showIconsInRecentConversationTrayEnabled.subscribe(
recentThreadObserver
);
app.settings?.trayEnabled.subscribe(recentThreadObserver);

// keep trying to get an image that isnt blank until they load
const interval = setInterval(() => {
Expand All @@ -95,102 +26,61 @@ window.addEventListener("load", () => {
) as HTMLCanvasElement | null;

if (canvas != null && canvas.toDataURL() != INITIAL_ICON_IMAGE) {
console.log(canvas.toDataURL());
recentThreadObserver();
// refresh for profile image loads after letter loads.
setTimeout(recentThreadObserver, 3000);
clearInterval(interval);
}
}
}, 250);
conversationListObserver.disconnect();
}

const title = document.head.querySelector("title");
if (title != null) {
title.innerText = "Android Messages";
}
});

conversationListObserver.observe(document.body, {
attributes: false,
subtree: true,
childList: true,
});

// a work around issue #229 (https://github.com/OrangeDrangon/android-messages-desktop/issues/229)
if (
!(app.settings?.startInTrayEnabled.value && app.settings?.trayEnabled.value)
) {
app.mainWindow?.show();
}

// Note: this hides this during dev
// remove the condition for testing
if (!IS_DEV && !app.settings?.seenResetSettingsWarning.value) {
const message = `
The settings for this app have been reset.
This is a one time occurance and is the result of behind the scenes work to clean up the code.
You may notice three missing settings:
- Enter to Send: Moved to the 3 dots menu
- Notification Sound: Moved to the 3 dots menu
- Use System Theme: Removed for the time being in favor of manual operation
`;
dialog.showMessageBox({
type: "info",
buttons: ["OK"],
title: "Settings Reset",
message,
});
app.settings?.seenResetSettingsWarning.next(true);
}
});

/**
* Override the webview's window's instance of the Notification class and forward their data to the
* main process. This is Necessary to generate and send a custom notification via Electron instead
* of just forwarding the webview (Google) ones.
*
* Derived from:
* https://github.com/electron/electron/blob/master/docs/api/ipc-main.md#sending-messages
* https://stackoverflow.com/questions/2891096/addeventlistener-using-apply
* https://stackoverflow.com/questions/31231622/event-listener-for-web-notification
* https://stackoverflow.com/questions/1421257/intercept-javascript-event
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
const OldNotification = window.Notification;

// @ts-ignore
window.Notification = function (title: string, options: NotificationOptions) {
const icon = getProfileImg(title);

const notificationOpts: NotificationConstructorOptions = app.settings
?.hideNotificationContentEnabled.value
const hideContent = ipcRenderer.sendSync("should-hide-notification-content");

const notificationOpts: NotificationOptions = hideContent
? {
title: "New Message",
body: "Click to open",
icon: path.resolve(RESOURCES_PATH, "icons", "64x64.png"),
}
: {
title,
icon,
icon: icon?.toDataURL(),
body: options.body || "",
};

// let google handle making the noise
notificationOpts.silent = true;

const notification = new ElectronNotification(notificationOpts);
notification.addListener("click", () => {
app.mainWindow?.show();
const newTitle = hideContent ? "New Message" : title;
const notification = new OldNotification(newTitle, notificationOpts);
notification.addEventListener("click", () => {
ipcRenderer.send("show-main-window");
document.dispatchEvent(new Event("focus"));
});
// Mock the api for adding event listeners for a normal Browser notification
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
notification.addEventListener = notification.addListener;
notification.show();
if (!app.mainWindow?.isFocused()) {
app.mainWindow?.flashFrame(true);
}
ipcRenderer.send("flash-main-window-if-not-focused");
return notification;
};
// THIS IS NEEDED FOR GOOGLE TO ISSUE NOTIFICATIONS
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Notification.permission = "granted";
Notification.requestPermission = async () => "granted";

window.Notification.requestPermission = async () => "granted";
//@ts-ignore
window.Notification.permission = "granted";

ipcRenderer.on("focus-conversation", (event, i) => {
focusFunctions[i]();
});
Loading

0 comments on commit bbcc621

Please sign in to comment.