background.coffee | |
---|---|
iOrder | |
Private constants |
|
Domain of this extension's homepage. |
HOMEPAGE_DOMAIN = 'neocotic.com'
|
Base URL (incl. domain and path) of order pages on the Apple US store. |
ORDER_URL = 'https://store.apple.com/us/order/guest/'
|
URL of the Apple US store orders list for an account. |
ORDERS_URL = 'https://store.apple.com/us/order/list'
|
List of recognised order status used by the Apple US store. |
STATUS = [
'We\'ve received your order'
'Processing Items'
'Preparing for Shipment'
'Shipped'
'Complete'
]
|
Base URL (incl. domain) of the track link on the Apple US store order page. |
TRACKER_URL = 'https://applestore.bridge-point.com/'
|
Private variables |
|
Number of status updates since the user last cleared it. |
updates = 0
|
Current version of iOrder. |
version = ''
|
Private functions |
|
Build the HTML to populate the popup with to optimize popup loading times. |
buildPopup = ->
errors = no
footer = $ '<footer/>'
footerF = $ '<div/>'
footerL = $ '<div/>'
header = $ '<header/>'
headerF = $ '<div/>'
headerL = $ '<div/>'
table = $ '<table id="orders"/>'
tbody = $ '<tbody/>'
|
Add two empty divs to both the header and footer. |
footer.append footerF, footerL
header.append headerF, headerL
|
Add the header links (Options, Orders). |
headerL.append $ '<a/>',
href: '#'
id: 'optionsLink'
onclick: 'popup.options()'
text: utils.i18n 'options_text'
title: utils.i18n 'options_title'
headerL.append $ '<a/>',
href: '#'
id: 'ordersLink'
onclick: 'popup.viewAll()'
text: utils.i18n 'orders_text'
title: utils.i18n 'orders_title'
|
Add the refresh button to the footer (can be removed though). |
footerF.append $ '<button/>',
id: 'refreshLink'
onclick: 'popup.refresh()'
text: utils.i18n 'refresh_text'
title: utils.i18n 'refresh_title'
|
Change the refresh button to show I'm busy... I am y'know. |
if updateManager.updating
footerF.find('button:first-child').attr(
disabled: 'disabled'
title: utils.i18n 'refreshing_title'
).html utils.i18n 'refreshing_text'
|
Add the clear button if badges are visibile; they can be distracting. |
if updates and utils.get 'badges'
footerF.append $ '<button/>',
id: 'clearLink'
onclick: 'popup.clear()'
text: utils.i18n 'clear_text'
title: utils.i18n 'clear_title'
|
Add the update details to the footer. |
footerL.append $ '<span/>',
text: utils.i18n('popup_footer_text', [
new Date(utils.get 'lastUpdated').format 'H:i'
getFrequency().text
])
|
Add the column headers to the orders table. |
$('<thead/>').append(
$.prototype.append.apply $('<tr/>'), [
$ '<th/>', text: utils.i18n 'order_header'
$ '<th/>', text: utils.i18n 'status_header'
$ '<th/>', text: utils.i18n 'actions_header'
]
).appendTo table
|
Add the table body which will contain the orders. |
table.append tbody
|
No orders exist so fill the blank and hide the refresh link. |
unless ext.orders.length
$('<tr/>').append(
$('<td/>',
class: 'empty'
colspan: 3
).html utils.i18n 'no_orders_text'
).appendTo tbody
footer.find('#refreshLink').remove()
|
Otherwise; let's create a row for each order. |
for order in ext.orders
tbody.append(
$.prototype.append.apply $('<tr/>'), [
$.prototype.append.apply $('<td/>'), [
$ '<strong/>', text: order.label
'<br />'
$ '<a/>',
'data-order-code': order.code
'data-order-number': order.number
href: '#'
onclick: 'popup.view(this)'
text: order.number
title: utils.i18n 'order_title'
]
$('<td/>').append $ '<span/>', text: getStatusText order
]
)
|
Order had an error; I suppose I should tell the user. |
if order.error
errors = yes
tbody.find('tr:last-child td:first-child strong').attr
class: 'error'
title: utils.i18n order.error
|
Found the track link so I'll share it with the user. I'm good like that. |
if order.trackingUrl
tbody.find('tr:last-child').append $('<td/>').append $ '<a/>',
'data-order-code': order.code
'data-order-number': order.number
href: '#'
onclick: 'popup.track(this)'
text: utils.i18n 'track_text'
title: utils.i18n 'track_title'
else
tbody.find('tr:last-child').append $('<td/>').append ' '
|
One or more order has an error so I'll add a little icon. |
if errors
$('<img/>',
height: 14
id: 'errorIcon'
src: '../images/exclamation_red.png'
title: utils.i18n 'update_errors_text'
width: 14
).prependTo footerL
ext.popupHtml = $('<div/>').append(header, table, footer).html()
|
Inject and execute |
executeScriptsInExistingTabs = (tabs) ->
for tab in tabs when tab.url.indexOf(HOMEPAGE_DOMAIN) isnt -1
chrome.tabs.executeScript tab.id, file: 'lib/install.js'
|
Inject and execute |
executeScriptsInExistingWindows = ->
chrome.windows.getAll null, (windows) ->
for win in windows
chrome.tabs.query windowId: win.id, executeScriptsInExistingTabs
|
Attempt to retrieve the details for the persisted update frequency. |
getFrequency = ->
frequency = utils.get 'frequency'
return freq for freq in ext.FREQUENCIES when freq.value is frequency
|
Return the number of status updates detected by this extension for an order since the specified time. |
getOrderStatusUpdates = (order, lastRead) ->
count = 0
count++ for update in order.updates when update.timeStamp > lastRead
return count
|
Return the URL of the page on the Apple US store for the specified order. |
getOrderUrl = (order) ->
encode = encodeURIComponent
return "#{ORDER_URL}#{encode order.number}/#{encode order.code}"
|
Attempt to derive the status text to be displayed for the order. |
getStatusText = (order) ->
length = order.updates?.length
return ' ' unless length
return order.updates[length - 1].status
|
Return the total number of detected status updates for all existing orders since the last time badges were cleared. |
getStatusUpdates = ->
count = 0
lastRead = utils.get 'lastRead'
count += getOrderStatusUpdates order, lastRead for order in ext.orders
return count
|
Return all windows managed by this extension that are displaying a page that begins with the specified URL. |
getWindows = (url) ->
return chrome.extension.getViews(type: 'tab').filter (element) ->
return element.location.href.indexOf(url) is 0
|
Handle the conversion/removal of older version of settings that may have
been stored previously by |
init_update = ->
update_progress = utils.get 'update_progress'
update_progress.settings ?= []
|
Check if the settings need updated for 1.1.0. |
if update_progress.settings.indexOf('1.1.0') is -1
|
Update the settings for 1.1.0. |
freq = ext.FREQUENCIES[1].value
frequency = utils.get 'frequency'
utils.set 'frequency', freq if frequency > -1 and frequency < freq
|
Ensure that settings are not updated for 1.1.0 again. |
update_progress.settings.push '1.1.0'
utils.set 'update_progress', update_progress
|
Initialize the persisted managed orders. |
initOrders = ->
utils.init 'orders', []
ext.orders = utils.get 'orders'
|
Determine whether or not an order already has the specified status. |
isOrderStatusNew = (order, status) ->
return no for update in order.updates when update.status is status
return yes
|
Determine whether or not the specified status is recognised by iOrder. |
isValidOrderStatus = (status) ->
return status in STATUS
|
Ensure any badge notification is cleared. |
markRead = (retainTimeStamp) ->
updates = 0
utils.set 'lastRead', $.now() unless retainTimeStamp
setBadge()
|
Update the UI so the clear button vanishes. |
updatePopup()
|
Attempt to notify the user of any unread status updates. |
notify = ->
oldUpdates = updates
updates = getStatusUpdates()
|
Update/clear badge depending on setting and updates available. |
setBadge if utils.get 'badges' then updates or ''
|
Show the notification if setting enabled and has new updates. |
if updates > oldUpdates and utils.get 'notifications'
webkitNotifications.createHTMLNotification(
chrome.extension.getURL 'pages/notification.html'
).show()
|
Listener for internal requests. |
onRequest = (request, sender, sendResponse) ->
order = {}
url = ''
|
Check what needs to be done... and then do it. |
switch request.type
when 'clear' then markRead()
when 'options'
|
Try using existing tabs for the options page before creating one. |
url = chrome.extension.getURL 'pages/options.html'
selectOrCreateTab url, (isNew) ->
return if isNew
win.options.refresh() for win in getWindows url
when 'refresh' then updateManager.restart()
when 'track'
order = ext.getOrder request.data.number, request.data.code
chrome.tabs.create url: order.trackingUrl if order and order.trackingUrl
when 'viewAll' then chrome.tabs.create url: ORDERS_URL
when 'view'
order = ext.getOrder request.data.number, request.data.code
chrome.tabs.create url: getOrderUrl order if order
|
Attempt to select a tab in the current window displaying a page whose
location begins with the specified URL. |
selectOrCreateTab = (url, callback) ->
chrome.windows.getCurrent (win) ->
chrome.tabs.query windowId: win.id, (tabs) ->
|
Try to find an existing tab. |
for tab in tabs
if tab.url.indexOf(url) is 0
existingTab = tab
break
if existingTab
|
Found one! Now to select it. |
chrome.tabs.update existingTab.id, selected: yes
callback? no
else
|
Ach well, let's just create a new one. |
chrome.tabs.create url: url
callback? yes
|
Set the badge text to the specified string. |
setBadge = (str = '') ->
chrome.browserAction.setBadgeText text: String str
|
Send an AJAX request to the specified order's page on the Apple US store and
parses the response to update the order's properties. |
updateOrder = (order, callback) ->
$.get(getOrderUrl(order), (data) ->
|
Probably won't happen; more of a sanity check. |
return order.error = 'update_invalid_page_error' unless data
|
Extract the relevant elements wrapped in jQuery goodness. |
heading = $(data).find '.order .delivery-group .sb-heading'
status = heading.find 'h4 span:first-child'
trackingUrl = heading.find ".group-actions tr:first-child td
a[href^='#{TRACKER_URL}']"
|
Dig deeper and try and get the actual values. |
status = if status.length then status.text() else ''
trackingUrl = if trackingUrl.length then trackingUrl.attr 'href' else ''
if status
|
A possible status was found but is it valid? |
if isValidOrderStatus status
|
OK, it was valid; but is it new??? |
if isOrderStatusNew order, status
|
Right! It was valid and new! Just add it already. |
order.updates.push
status: status
timeStamp: $.now()
else
|
Extension could need updated if it gets here. |
return order.error = 'update_invalid_status_error'
else
|
Bad user data or extension could need updated. |
return order.error = 'update_status_not_found_error'
|
Only update the Track link if it was found. |
order.trackingUrl = trackingUrl if trackingUrl
|
Clear any pre-existing errors. |
order.error = ''
).error(->
|
Something went wrong. |
order.error = 'update_page_not_found_error'
).complete ->
|
Done! Now let's tell the boss. |
callback?.apply updateManager, [order]
|
Build the HTML to populate the popup with to optimize popup loading times and updates any popup currently being displayed. |
updatePopup = ->
buildPopup()
popup = chrome.extension.getViews(type: 'popup')[0]
popup.document.body.innerHTML = ext.popupHtml if popup
|
Update Manager setup |
|
Central manager for updating the orders. |
updateManager =
|
Unique interval identifier used to managed repeating updates. |
id: undefined
|
Message stack used by the current update process. |
messages: []
|
Indicate whether or not the update manager is currently running through an update process. |
updating: no
|
Restart the update manager, which may run once or start a repeating cycle
based on the current update frequency. |
restart: ->
frequency = utils.get 'frequency'
|
I'm busy; I'll do it later. |
return this.messages.push 'restart' if @updating
|
Clear the current cycle, where applicable. |
if @id
clearInterval @id
|
Ensure that it is disabled, where applicable. |
@id = undefined if frequency is -1
|
Start a new cycle if required. |
@id = setInterval @run, frequency if frequency isnt -1
|
Run the initial process. |
@run()
|
Core of the update manager which actually performs the update process. |
run: ->
progress = 0
@updating = yes
|
Update the UI to show that I'm busy. |
notify()
updatePopup()
|
Called when the AJAX request has been parsed and read for each order. |
updated = (order) ->
progress++
|
Check if all orders have been updated; supports no orders. |
if progress >= ext.orders.length
@updating = no
|
Persist orders and update time stamp. |
utils.set 'orders', ext.orders
utils.set 'lastUpdated', $.now()
|
Update the UI again to reflect the changes. |
notify()
updatePopup()
|
Now read the message stack. |
this[message]?() for message in @messages
@messages = []
|
Finish this now since no orders exist. |
updated.apply this unless ext.orders.length
|
Update each order by parsing its page on the Apple US store. |
updateOrder order, updated for order in ext.orders
|
Start the update manager, which may run once or start a repeating cycle based on the current update frequency. |
start: ->
frequency = utils.get 'frequency'
|
Clear the current cycle, where applicable. |
if frequency is -1
if @id
clearInterval @id
@id = undefined
else
|
I start twice for no one... see RESTART for that. |
return if @id
|
Start a new cycle. |
@id = setInterval @run, frequency
|
Run the initial process. |
@run()
|
Stop the update manager. |
stop: ->
|
I'm busy; I'll do it later. |
return @messages.push 'stop' if @updating
|
Clear the current cycle, where possible. |
if @id
clearInterval @id
@id = undefined
|
Background page setup |
ext = window.ext =
|
Public constants |
|
Details for the supported update frequencies. |
FREQUENCIES: [
text: utils.i18n 'freq_disabled'
value: -1
,
text: utils.i18n 'freq_minutes', '15'
value: 15 * 60 * 1000
,
text: utils.i18n 'freq_minutes', '30'
value: 30 * 60 * 1000
,
text: utils.i18n 'freq_minutes', '45'
value: 45 * 60 * 1000
,
text: utils.i18n 'freq_hour'
value: 60 * 60 * 1000
,
text: utils.i18n 'freq_hours', '2'
value: 2 * 60 * 60 * 1000
]
|
Public variables |
|
List of orders currently being maintained. |
orders: []
|
Pre-prepared HTML for the popup to be populated using. |
popupHtml: ''
|
Public functions |
|
Attempt to return the order whose details match the specified criteria. |
getOrder: (number, code) ->
for order in ext.orders when order.number is number and order.code is code
return order
|
Initialize the background page. |
init: ->
utils.init 'update_progress', {}
init_update()
utils.init 'badges', on
utils.init 'frequency', ext.FREQUENCIES[1].value
utils.init 'lastRead', $.now()
utils.init 'lastUpdated', $.now()
utils.init 'notifications', on
utils.init 'notificationDuration', 6 * 1000
initOrders()
chrome.extension.onRequest.addListener onRequest
|
It's nice knowing what version is running. |
$.getJSON chrome.extension.getURL('manifest.json'), (data) ->
version = data.version
|
Execute content scripts now that we know the version. |
executeScriptsInExistingWindows()
|
It's alive! |
updateManager.start()
|