Chrome – Amazon Deterrent and Whackamole (in progress)

Extensions

First extension is simple – it just replaces all images on amazon with a simple message of advice:
Screen Shot 2017-02-19 at 1.58.32 PM

Second one is an unfinished attempt to make a window based whack-a-mole game. So far, I’ve only implemented opening up a new window that jumps to a new location when you click it. Ideally it would pass a message back to the background page that increments a counter to keep score, which would then be displayed in a separate small window. We’ll get there.
whack-a-mole

Code for extensions are here and here

Reverse Engineering

I spent a while trying to find an extension and eventually came across this really simple one that reminds you to drink water – Drink!. It’s a small, unminified code base and super easy to deconstruct.

Looking at the manifest:


{
"update_url": "https://clients2.google.com/service/update2/crx",

  "name": "Drink!",
  "short_name": "Drink!",
  "version": "0.3.2",
  "minimum_chrome_version": "29",
  "manifest_version": 2,
  "description": "Drink! is a simple drink reminder so you never forget to drink a little every once in a while.",
  "author": "Markus Ölhafen",
  "homepage_url": "http://markusoelhafen.com",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "background": {
    "scripts": [
      "background.js"
    ],
    "persistent": true
  },
  "omnibox": { "keyword" : "drink" },
  "browser_action": {
    "default_icon": "icons/icon19.png",
    "default_title": "Drink",
    "default_popup": "index.html"
  },
  "permissions": [
    "notifications",
    "storage"
  ],
  "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'"
}

It uses a background page and a popup, as well as an omnibox keyword, which allows the user to type “drink” into the omnibox and hit tab, and then enter some data. The background page is persistent, because it’s a counter, it needs to be running all the time, so that makes sense.

The background code:


var secondsSet, autoStart, currentSeconds = false;

// COUNTDOWN

function run(){
	countdown(secondsSet);
	chrome.browserAction.setIcon({path: '/icons/icon19_active.png'});
	//console.log("starting countdown");
}

function stop(){
	try{
		clearInterval(timer);
	} catch(e){
		console.log("no timer defined..");
	}
	currentSeconds = false;
	clearNotification();
	chrome.browserAction.setIcon({path: '/icons/icon19.png'});
	//console.log("stopping countdown");
}

function countdown(seconds) {
	currentSeconds = seconds;
	timer = setInterval(function() {
		if(currentSeconds >= 0) {
			//console.log("seconds: " + currentSeconds);
			chrome.runtime.sendMessage({seconds: currentSeconds});
		} else {
			// clearInterval(timer);
			// currentSeconds = false;
			stop();
			createNotification();
			updateNotification();
		}
		currentSeconds--;
	}, 1000);
}

// NOTIFICATIONS

function buttonClicked(notId, button) { // buttonIndex: 0 = ok || 1 = shut up
	if (button == 0) {
		clearInterval(updateLoop);
		clearNotification();
		run();
	}
	else if (button == 1) {
		stop();
	}
}

function createNotification() {
	var opt = {
		type: "basic",
		title: "IT'S TIME TO DRINK!",
		message: "Drink some water now.",
		iconUrl: "../icons/popup_icon.png",
		priority: 2,
		buttons: [{
			title: "Ok, done. Restart."
		}, {
			title: "Shut up!"
		}]
	};
	chrome.notifications.create("popup", opt);
}

function updateNotification() { // update every 60 seconds
	updateLoop = setInterval(function() {
		chrome.notifications.update("popup", {priority: 0}, function() {
			chrome.notifications.update("popup", {priority: 2});
		});
		console.log("updated");
	}, 10000);
}

function clearNotification() { // clear all notifications
	chrome.notifications.getAll(function(cb) {
		for(var prop in cb){
			if(cb.hasOwnProperty(prop)) chrome.notifications.clear(prop);
		}
	});
}

// SYNC OPTIONS

function syncOptions() {
	chrome.storage.sync.get({secondsSet: '3600', autoStart: false}, function(options) {
		secondsSet = options.secondsSet;
		if(options.autoStart) run();
		//console.log("seconds set: " + secondsSet);
		//console.log("auto-start: " + options.autoStart);
	});
}

// LISTEN TO CONTENT

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
	if(request.Alarm == 'start') {
		run();
	} else if(request.Alarm == 'stop'){
		stop();
	} else if(request.Alarm == 'state') {
		sendResponse({seconds: currentSeconds});
	} else if(request.Options == 'saved') {
		syncOptions();
	} 
})

// VERSION UPDATE NOTIFICATION

chrome.runtime.onInstalled.addListener(function(installed) {
	//console.log(installed);
	var version = chrome.runtime.getManifest().version
	if(installed.reason == "update") {
		var opt = {
			type: "basic",
			title: "Drink! Update",
			message: "Drink! has been updated to version " + version + ". Get full changelog on chrome webstore.",
			iconUrl: "../icons/popup_icon.png",
			priority: 1
		}
		chrome.notifications.create("update", opt);
	}
});

// ON LOAD

onload = function() {
	syncOptions();
	chrome.notifications.onButtonClicked.addListener(buttonClicked);
}

It’s all very simple, the button in the popup starts a countdown timer. On each iteration of the timer, a message is passed using chrome.runtime.sendMessage. When the counter gets to zero, it calls another function that creates a popup.

Leave a Reply

Your email address will not be published. Required fields are marked *