From 87b65502fc5cff11c8be091dd58eb010f04a40dd Mon Sep 17 00:00:00 2001 From: vrde Date: Wed, 2 Nov 2016 01:22:00 +0100 Subject: [PATCH] Start working on Docco (run npm i) --- .gitignore | 1 + build-docs.sh | 8 +++++ docs/bootstraprc | 29 ----------------- package.json | 4 ++- src/app.js | 65 +++++++++++++++++++++++++++++++++++++-- src/background/account.js | 15 +++++---- src/background/db.js | 2 +- src/utils.js | 2 +- 8 files changed, 83 insertions(+), 43 deletions(-) create mode 100755 build-docs.sh delete mode 100644 docs/bootstraprc diff --git a/.gitignore b/.gitignore index 5825dfe..2ee55bc 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,6 @@ build/* dist/* +docs/* node_modules/* diff --git a/build-docs.sh b/build-docs.sh new file mode 100755 index 0000000..5d73a6b --- /dev/null +++ b/build-docs.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +ROOT=./src +for DIR in `find $ROOT -type d`; do + INDIR=$DIR/* + OUTDIR=docs${DIR:${#ROOT}}; + ./node_modules/.bin/docco $INDIR -o $OUTDIR +done; diff --git a/docs/bootstraprc b/docs/bootstraprc deleted file mode 100644 index e33fad3..0000000 --- a/docs/bootstraprc +++ /dev/null @@ -1,29 +0,0 @@ ---- -bootstrapVersion: 3 - -styleLoaders: - - style - - css?sourceMap - - postcss - - sass?sourceMap&output=expanded&precision=8 - -styles: - # Always needs to be included - mixins: true - - # Utilities - utilities: true - - # Normalize - normalize: true - scaffolding: true - - # Selected stylesheets - buttons: true - close: true - component-animations: true - forms: true - modals: true - popovers: true - -scripts: false diff --git a/package.json b/package.json index 775dedd..6cf477f 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "build:dist": "./pack-extension.sh || true", "clean": "rimraf ./build ./dist", "test": "mocha --compilers js:babel-register --reporter spec", - "test:watch": "mocha -w --compilers js:babel-register --reporter spec" + "test:watch": "mocha -w --compilers js:babel-register --reporter spec", + "docs": "rimraf ./docs && ./build-docs.sh" }, "devDependencies": { "autoprefixer": "^6.4.0", @@ -40,6 +41,7 @@ "chai": "^3.5.0", "cross-env": "^2.0.0", "css-loader": "^0.23.1", + "docco": "^0.7.0", "dotenv": "^2.0.0", "eslint": "^3.3.1", "eslint-config-ascribe-react": "^1.4.0", diff --git a/src/app.js b/src/app.js index f39389a..1d2f319 100644 --- a/src/app.js +++ b/src/app.js @@ -1,15 +1,38 @@ -// Install any necessary polyfills into global, such as es6, stage/3, stage/4, etc. as needed +// # Welcome to the extension docs! +// Here you can learn how the extension works and, if this is what you aim for, +// where to put your hands to hack the code. +// +// ## Structure of the extension +// The extension has two parts: +// - a content script +// - event pages. // +// The **content script** is the JavaScript code injected into the Facebook.com +// website. It can interact with the elements in the page to scrape the data and +// prepare the payload to be sent to the API. +// +// On the other side there are **event pages**. They are scripts triggered by +// some events sent from the **content script**. Since they run in *browser-space*, +// they have the permission (if granted) to do cross-domain requests, access +// cookies, and [much more](https://developer.chrome.com/extensions/declare_permissions). +// All **event pages** are contained in the [`./background`](./background/app.html) folder. +// (the name is **background** for historical reasons and it might be subject of changes +// in the future). + +// # Code +// Import the styles for the app. require('../styles/app.scss'); +// Install any necessary polyfills into global, such as es6, stage/3, stage/4, etc. as needed import 'core-js/es6'; +// Import the react toolkit. // Seems like importing 'react-dom' is not enough, we need to import 'react' as well. import React from 'react'; import ReactDOM from 'react-dom'; import ReactDOMServer from 'react-dom/server'; -// import nacl from 'tweetnacl'; +// Import other utils to handle the DOM and scrape data. import uuid from 'uuid'; import $ from 'jquery'; import 'arrive'; @@ -23,16 +46,25 @@ import { registerHandlers } from './handlers/index'; import StartButton from './components/startButton'; import OnboardingBox from './components/onboardingBox'; +// Boot the user script. This is the first function called. +// Everything starts from here. function boot () { console.log(`Fbtrex version ${config.VERSION} build ${config.BUILD} loading.`); - // Source handlers so they can process events + // Register all the event handlers. + // An event handler is a piece of code responsible for a specific task. + // You can learn more in the [`./handlers`](./handlers/index.html) directory. registerHandlers(hub); + // Lookup the current user and decide what to do. userLookup(response => { + // `response` contains the user's public key and its status, + // if the key has just been created, the status is `new`. if (response.status === 'new') { + // In the case the status is `new` then we need to onboard the user. onboarding(response.publicKey); } else { + // Otherwise, we load all the components of the UI and the watchers. render(); timeline(); prefeed(); @@ -42,10 +74,18 @@ function boot () { }); } +// The function `userLookup` communicates with the **action pages** +// to get information about the current user from the browser storage +// (the browser storage is unreachable from a **content script**). function userLookup (callback) { + // Extract the data from the DOM. const basicInfo = scrapeUserData($('body')); + // Set the `userId` in the global configuration object. config.userId = basicInfo.id; + // Propagate the data to all the handlers interested in that event. hub.event('user', basicInfo); + // Finally, retrieve the user from the browser storage. This is achieved + // sending a message to the `chrome.runtime`. chrome.runtime.sendMessage({ type: 'userLookup', payload: { @@ -101,20 +141,39 @@ function processTimeline () { hub.event('newTimeline', { uuid: uuid.v4(), dt: getTimeISO8601() }); } +// The function `onboarding` guides the user through the public key +// registration. +// The flow is the following: +// 1. display a message at the top of the page. The message includes the +// a public key and it prompts the user to copy paste it in a +// new public post. +// 2. Wait until a post appears in the timeline. +// 3. Once the post appears, extract its permalink and send it to the API. +// 4. If the API call is successful, an **activity page** will update the +// status of the key from `new` to `verified`. function onboarding (publicKey) { + // The first action is to display the big information box. $('#mainContainer').prepend($(ReactDOMServer.renderToString( ))); + // Then we listen to all the new posts appearing on the user's timeline. document.arrive('#contentCol .userContentWrapper', function () { const $elem = $(this).parent(); + // Process the post only if its html contains the user's public key. if ($elem.html().indexOf(publicKey) !== -1) { + // Extract the URL of the post. var permalink = $elem.find('[href^="/permalink.php"]') .attr('href'); console.log('permalink', permalink); + // Kindly ask to verify the user's public key against the API. + // Since this is a cross domain request, we need to delegate the + // call to an **action page**. If the call is successful, the action + // page handling the event will update the status of the key in the + // database. chrome.runtime.sendMessage({ type: 'userVerify', payload: { diff --git a/src/background/account.js b/src/background/account.js index 04f148a..c91e1b1 100644 --- a/src/background/account.js +++ b/src/background/account.js @@ -38,12 +38,11 @@ function userLookup ({ userId }, sendResponse) { }; function userVerify ({ permalink, publicKey, userId }, sendResponse) { - api - .register({ permalink, publicKey }) - .catch/* 'then' */(response => { - update(userId, { status: 'verified' }) - .then(response => sendResponse('ok')) - .catch(response => sendResponse('ok'/* 'error' */)); - }); - // .catch(response => sendResponse('error')); + api.register({ permalink, publicKey }) + .catch/* 'then' */(response => { + update(userId, { status: 'verified' }) + .then(response => sendResponse('ok')) + .catch(response => sendResponse('ok'/* 'error' */)); + }); + // .catch(response => sendResponse('error')); }; diff --git a/src/background/db.js b/src/background/db.js index 2f2b704..78248f5 100644 --- a/src/background/db.js +++ b/src/background/db.js @@ -9,7 +9,7 @@ export function get (key, setIfMissing) { backend.get(key, val => { if (chrome.runtime.lastError) { reject(chrome.runtime.lastError); - } else if (isEmpty(val)) { + } else if (isEmpty(val) && !isEmpty(setIfMissing)) { var newVal = isFunction(setIfMissing) ? setIfMissing(key) : setIfMissing; backend.set(newVal, () => resolve(newVal)); } else { diff --git a/src/utils.js b/src/utils.js index 5971bcd..9b203a5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -27,7 +27,7 @@ export function normalizeUrl (url) { } export function isEmpty (object) { - return object === null || Object.keys(object).length === 0; + return object === null || object === undefined || Object.keys(object).length === 0; } export function isFunction (value) {