Skip to content

Commit

Permalink
apply new sync / auth logic & migrate functions
Browse files Browse the repository at this point in the history
  • Loading branch information
cnwangjie committed Nov 1, 2018
1 parent 1a42942 commit 1d16421
Show file tree
Hide file tree
Showing 17 changed files with 306 additions and 108 deletions.
8 changes: 6 additions & 2 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
module.exports = {
development: {
__CLIENT_ID__: '530831729511-eq8apt6dhjimbmdli90jp2ple0lfmn3l.apps.googleusercontent.com',
__DEV_CSP__: process.env.MOZ ? '' : ' http://localhost:8098 chrome-extension://nhdogjmejiglipccpnnnanhbledajbpd'
__DEV_CSP__: process.env.MOZ ? '' : ' http://localhost:8098 chrome-extension://nhdogjmejiglipccpnnnanhbledajbpd',
__EXT_NAME__: 'better-onetab (dev)',
__CONTENT_SCRIPTS_MATCHES__: process.env.MOZ ? '*://*/*' : 'http://127.0.0.1:3000/*',
},
production: {
__CLIENT_ID__: '530831729511-dclgvblhv7var13mvpjochb5f295a6vc.apps.googleusercontent.com',
__DEV_CSP__: ''
__DEV_CSP__: '',
__EXT_NAME__: '__MSG_ext_name__',
__CONTENT_SCRIPTS_MATCHES__: 'https://boss.cnwangjie.com/*',
}
}
6 changes: 6 additions & 0 deletions src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,12 @@
"ui_not_login": {
"message": "Not login"
},
"ui_offline": {
"message": "You are offline"
},
"ui_refresh": {
"message": "Refresh"
},
"ui_unauth": {
"message": "Unauthorized"
},
Expand Down
33 changes: 14 additions & 19 deletions src/background.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import _ from 'lodash'
import __ from './common/i18n'
import tabs from './common/tabs'
import storage from './common/storage'
import options from './common/options'
import _ from 'lodash'
import __ from './common/i18n'
import browser from 'webextension-polyfill'
import migrate from './common/migrate'
import boss from './common/service/boss'
import browser from 'webextension-polyfill'


/* eslint-disable-next-line */
if (DEBUG && !MOZ) import(
Expand All @@ -21,6 +23,7 @@ if (PRODUCTION) import(
if (DEBUG) {
window.tabs = tabs
window.browser = browser
browser.browserAction.setBadgeText({text: 'dev'})
}

const getBrowserActionHandler = action => {
Expand Down Expand Up @@ -241,23 +244,15 @@ const init = async () => {
}
if (PRODUCTION) ga('send', 'event', 'Popup item clicked')
}
if (msg.uploadImmediate) {
boss.uploadImmediate().catch(() => {
browser.runtime.sendMessage({uploaded: {error: true}})
})
}
if (msg.forceUpdate) {
boss.forceUpdate(msg.forceUpdate)
}
if (msg.resolveConflict) {
boss.resolveConflict(msg.resolveConflict)
}
if (msg.forceDownload) {
boss.forceDownloadRemoteImmediate()
}
if (msg.storeInto) {
tabs.storeSelectedTabs(msg.storeInto.index)
}
if (msg.login) {
boss.login(msg.login.token)
}
if (msg.refresh) {
boss.refresh()
}
})
browser.runtime.onMessageExternal.addListener(commandHandler)
browser.commands.onCommand.addListener(commandHandler)
Expand Down Expand Up @@ -299,8 +294,8 @@ const init = async () => {
setupContextMenus(await storage.getOptions())
}
})
await boss.forceDownloadRemoteImmediate()
setInterval(() => boss.forceDownloadRemoteImmediate(), 60 * 1000)
await migrate()
await boss.refresh()
}

init()
4 changes: 4 additions & 0 deletions src/common/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
export const PICKED_TAB_PROPS = ['url', 'title', 'favIconUrl', 'pinned']
export const PICKED_LIST_RPOPS = ['_id', 'tabs', 'title', 'time', 'pinned', 'expand', 'color']
export const SYNCED_LIST_PROPS = ['_id', 'tabs', 'title', 'time', 'pinned', 'color']

export const TOKEN_KEY = 'token'
export const AUTH_HEADER = 'auth'

export const END_FRONT = 'front'
export const END_BACKGROUND = 'background'
4 changes: 2 additions & 2 deletions src/common/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from './constants'
import {genObjectId} from './utils'

const createNewTabList = ({tabs, title, time}) => ({
export const createNewTabList = ({tabs, title, time}) => ({
_id: genObjectId(),
tabs: tabs || [],
title: title || '',
Expand All @@ -17,7 +17,7 @@ const createNewTabList = ({tabs, title, time}) => ({
})

// Preserving the needed properties before store lists.
const normalize = list => {
export const normalize = list => {
const normalizedList = _.pick(list, PICKED_LIST_RPOPS)
normalizedList.tabs = normalizedList.tabs.map(tab => _.pick(tab, PICKED_TAB_PROPS))
return normalizedList
Expand Down
85 changes: 72 additions & 13 deletions src/common/listManager.js
Original file line number Diff line number Diff line change
@@ -1,65 +1,124 @@
import _ from 'lodash'
import browser from 'webextension-polyfill'
import {
SYNCED_LIST_PROPS,
END_FRONT,
END_BACKGROUND,
} from './constants'

const cache = { lists: null, ops: null }
const getStorage = async () => {
if (cache.lists && cache.ops) return cache
const {lists, ops} = await browser.storage.local.get(['lists', 'ops'])
cache.lists = lists
cache.ops = ops
cache.lists = lists || []
cache.ops = ops || []
return cache
}
const compressOps = ops => {
const removed = []
const updated = {}
const finalOps = []
console.log(ops)
for (let i = ops.length - 1; i > -1; i -= 1) {
const op = ops[i]
// ignore all actions for the list if that list will be removed finally
if (op.args && typeof op.args[0] === 'object' && removed.includes(op.args[0]._id)
|| typeof op.args[0] === 'string' && removed.includes(op.args[0])) continue

if (op.method === 'removeListById') {
removed.push(op.args[0])
finalOps.unshift(op)
} else if (op.method === 'updateListById') {
// keep the final result of every property if a list will be updated
if (updated[op.args[0]]) {
for (const key in op.args[1]) {
if (key in updated[op.args[0]]) continue
updated[op.args[0]][key] = op.args[1][key]
}
continue
} else {
updated[op.args[0]] = Object.assign({}, op.args[1])
finalOps.unshift({method: 'updateListById', args: [op.args[0], updated[op.args[0]]]})
}
} else if (op.method === 'changeListOrderRelatively') {
// combine the value if a list is reordered continuously
if (i > 0 && ops[i - 1].method === 'changeListOrderRelatively' && op.args[0] === ops[i - 1].args[0]) {
ops[i - 1].args[1] += ops[i].args[1]
} else finalOps.unshift(op)
} else {
// do nothing if add a list
finalOps.unshift(op)
}
}
return finalOps
}
const saveStorage = _.debounce(async () => {
cache.ops = compressOps(cache.ops)
await browser.storage.local.set(cache)
cache.lists = cache.ops = null
await browser.runtime.sendMessage({refresh: true})
}, 5000)
const manager = {}
manager.modifier = {
// lists modifier (return true if need to add ops)
manager.modifiers = {
addList(lists, [list]) {
lists.unshift(list)
return true
},
updateListById(lists, [listId, newList]) {
const normal = SYNCED_LIST_PROPS.some(k => Object.keys(newList).includes(k))
for (const list of lists) {
if (list._id !== listId) continue
for (const [k, v] of Object.entries(newList)) {
list[k] = v
}
return
return normal
}
},
removeListById(lists, [listId]) {
const index = lists.findIndex(list => list._id === listId)
lists.splice(index, 1)
return true
},
changeListOrderRelatively(lists, [listId, diff]) {
const index = lists.findIndex(list => list._id === listId)
const [list] = lists.splice(index, 1)
lists.splice(index + diff, 0, list)
return true
},
}
const addEventListener = () => browser.runtime.onMessage.addListener(({listModifed}) => {
const addEventListener = (receiveFrom, callback) => browser.runtime.onMessage.addListener(({listModifed, from}) => {
if (receiveFrom !== from) return
if (!listModifed) return
const {method, args} = listModifed
manager[method](args)
return callback(method, args)
})
const genMethods = isBackground => {
for (const [name, func] of Object.entries(manager.methods)) {
manager[name] = isBackground ? async (...args) => {
for (const [name, func] of Object.entries(manager.modifiers)) {
manager[name] = isBackground ? async (...args) => { // for background
console.debug('[list manager] modify list:', name, ...args)
const {lists, ops} = await getStorage()
ops.push({method: name, args, time: Date.now()})
func(lists, args)
if (func(lists, args)) ops.push({method: name, args, time: Date.now()})
saveStorage()
} : async (...args) => {
await browser.runtime.sendMessage({listModifed: {method: name, args}})
await browser.runtime.sendMessage({listModifed: {method: name, args}, from: END_BACKGROUND})
} : async (...args) => { // for front end
console.debug('[list manager] call to modify list:', name, ...args)
await browser.runtime.sendMessage({listModifed: {method: name, args}, from: END_FRONT})
}
}
}
manager.init = async () => {
if (manager.inited) return
const background = await browser.runtime.getBackgroundPage()
const isBackground = window === background
if (isBackground) await addEventListener()
if (isBackground) await addEventListener(END_FRONT, (method, args) => manager[method](...args))
genMethods(isBackground)
manager.inited = true
}
const receiver = []
manager.receiveBackgroundModified = async lists => {
if (receiver.includes(lists)) return
receiver.push(lists)
await addEventListener(END_BACKGROUND, (method, args) => manager.modifiers[method](lists, args))
}
export default manager
29 changes: 29 additions & 0 deletions src/common/migrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import _ from 'lodash'
import {normalize} from './list'
import options from './options'
import {genObjectId} from './utils'
import listManager from './listManager'
import browser from 'webextension-polyfill'
listManager.init()
const migrate = async () => {
const {version} = await browser.storage.local.get('version')
const {version: currentVersion} = browser.runtime.getManifest()
if (version !== currentVersion) await browser.storage.local.set({version: currentVersion})
if (version >= '1.4.0') return
// every list need an ID
const {lists} = await browser.storage.local.get('lists')
const {0: listsWithoutId, 1: listsWithId} = _.groupBy(lists.map(normalize), list => +!!list._id)
await browser.storage.local.set({lists: listsWithId})

for (const list of listsWithoutId.reverse()) {
list._id = genObjectId()
await listManager.addList(list)
}
// remove depracated options
const {opts} = await browser.storage.local.get('opts')
if (opts) {
await browser.storage.local.set({opts: _.pick(opts, _.keys(options.getDefaultOptions()))})
}
}

export default migrate
49 changes: 40 additions & 9 deletions src/common/service/boss.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '../constants'
import browser from 'webextension-polyfill'

const apiUrl = 'https://boss.cnwangjie.com'
const apiUrl = 'http://127.0.0.1:3000'

const hasToken = async () => TOKEN_KEY in await browser.storage.local.get(TOKEN_KEY)

Expand Down Expand Up @@ -78,10 +78,19 @@ const getInfo = () => fetchData('/api/info').then(info => {
return info
})
const getLists = () => fetchData('/api/lists')
const setLists = lists => fetchData('/api/lists', 'PUT', {lists})
const getOpts = () => fetchData('/api/opts')
const setOpts = opts => fetchData('/api/v2/opts', 'PUT', {opts})
const changeListBulk = changes => fetchData('/api/v2/lists/bulk', 'POST', {changes})

const uploadWholeLists = async () => {
const {lists} = await browser.storage.local.get('lists')
if (!lists) return
const result = await setLists(lists)
result.listsUpdatedAt = Date.parse(result.listsUpdatedAt)
return result
}

const uploadOperations = async () => {
const {ops} = await browser.storage.local.get('ops')
if (!ops) return
Expand All @@ -98,32 +107,35 @@ const uploadOperations = async () => {

const applyRemoteLists = async () => {
const lists = await getLists()
const {listsUpdatedAt} = browser.storage.local.set(lists)
const {listsUpdatedAt} = browser.storage.local.set({lists})
return Date.parse(listsUpdatedAt)
}

const uploadOpts = async () => {
const {opts} = await browser.storage.local.get('opts')
const optsUpdatedAt = await setOpts(opts)
const result = await browser.storage.local.set(optsUpdatedAt)
result.optsUpdatedAt = Date.parse(result.optsUpdatedAt)
return result
await browser.storage.local.set({optsUpdatedAt})
return Date.parse(optsUpdatedAt)
}

const applyRemoteOpts = async () => {
const opts = await getOpts()
return browser.storage.local.set(opts)
return browser.storage.local.set({opts})
}

const refresh = async () => {
if (!(await hasToken())) return
const remoteInfo = await getInfo()
const localInfo = await browser.storage.local.get(['listsUpdatedAt', 'optsUpdatedAt'])
localInfo.listsUpdatedAt = localInfo.listsUpdatedAt || 0
localInfo.optsUpdatedAt = localInfo.optsUpdatedAt || 0

// normal lists sync logic: apply local operations firstly
const {ops} = await browser.storage.local.get('ops')
if (ops && ops.length) {
if (remoteInfo.listsUpdatedAt === 0) {
const {listsUpdatedAt} = await uploadWholeLists()
await browser.storage.local.set({listsUpdatedAt})
} else if (ops && ops.length) {
// normal lists sync logic: apply local operations firstly
const {listsUpdatedAt} = await uploadOperations()
await browser.storage.local.set({listsUpdatedAt})
}
Expand All @@ -134,15 +146,34 @@ const refresh = async () => {
}

if (localInfo.optsUpdatedAt > remoteInfo.optsUpdatedAt) {
const {optsUpdatedAt} = await uploadOpts()
const optsUpdatedAt = await uploadOpts()
await browser.storage.local.set({optsUpdatedAt})
} else if (localInfo.optsUpdatedAt < remoteInfo.optsUpdatedAt) {
await applyRemoteOpts()
await browser.storage.local.set({optsUpdatedAt: remoteInfo.optsUpdatedAt})
}
await browser.runtime.sendMessage({refreshed: true})
}

const login = async token => {
if (await hasToken()) return
await setToken(token)
await getInfo()
const loginNotificationId = 'login'
browser.notifications.create(loginNotificationId, {
type: 'basic',
iconUrl: 'assets/icons/icon_128.png',
title: 'you have login to boss successfully',
message: '',
})
setTimeout(() => {
browser.notifications.clear(loginNotificationId)
}, 5000)
await refresh()
}

export default {
hasToken,
refresh,
login,
}
Loading

0 comments on commit 1d16421

Please sign in to comment.