-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wiring the views with the server. r=samgiles #14
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
.microphone { | ||
position: absolute; | ||
position: fixed; | ||
z-index: 1; | ||
top: 8rem; | ||
right: 9rem; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
'use strict'; | ||
|
||
const p = Object.freeze({ | ||
settings: Symbol('settings'), | ||
net: Symbol('net'), | ||
|
||
// Private methods. | ||
getURL: Symbol('getURL'), | ||
onceOnline: Symbol('onceOnline'), | ||
onceReady: Symbol('onceReady'), | ||
getChannelValues: Symbol('getChannelValues'), | ||
updateChannelValue: Symbol('updateChannelValue'), | ||
}); | ||
|
||
/** | ||
* Instance of the API class is intended to abstract consumer from the API | ||
* specific details (e.g. API base URL and version). It also tracks | ||
* availability of the network, API host and whether correct user session is | ||
* established. If any of this conditions is not met all API requests are | ||
* blocked until it's possible to perform them, so consumer doesn't have to | ||
* care about these additional checks. | ||
*/ | ||
export default class API { | ||
constructor(net, settings) { | ||
this[p.net] = net; | ||
this[p.settings] = settings; | ||
|
||
Object.freeze(this); | ||
} | ||
|
||
/** | ||
* Performs HTTP 'GET' API request and accepts JSON as response. | ||
* | ||
* @param {string} path Specific API resource path to be used in conjunction | ||
* with the base API path. | ||
* @return {Promise} | ||
*/ | ||
get(path) { | ||
return this[p.onceReady]() | ||
.then(() => this[p.net].fetchJSON(this[p.getURL](path))); | ||
} | ||
|
||
/** | ||
* Performs HTTP 'POST' API request and accepts JSON as response. | ||
* | ||
* @param {string} path Specific API resource path to be used in conjunction | ||
* with the base API path. | ||
* @param {Object=} body Optional object that will be serialized to JSON | ||
* string and sent as 'POST' body. | ||
* @return {Promise} | ||
*/ | ||
post(path, body) { | ||
console.log(path, body); | ||
|
||
return this[p.onceReady]() | ||
.then(() => this[p.net].fetchJSON(this[p.getURL](path), 'POST', body)); | ||
} | ||
|
||
/** | ||
* Performs HTTP 'PUT' API request and accepts JSON as response. | ||
* | ||
* @param {string} path Specific API resource path to be used in conjunction | ||
* with the base API path. | ||
* @param {Object=} body Optional object that will be serialized to JSON | ||
* string and sent as 'PUT' body. | ||
* @return {Promise} | ||
*/ | ||
put(path, body) { | ||
return this[p.onceReady]() | ||
.then(() => this[p.net].fetchJSON(this[p.getURL](path), 'PUT', body)); | ||
} | ||
|
||
/** | ||
* Performs HTTP 'DELETE' API request and accepts JSON as response. | ||
* | ||
* @param {string} path Specific API resource path to be used in conjunction | ||
* with the base API path. | ||
* @param {Object=} body Optional object that will be serialized to JSON | ||
* string and sent as 'DELETE' body. | ||
* @return {Promise} | ||
*/ | ||
delete(path, body) { | ||
return this[p.onceReady]() | ||
.then(() => this[p.net].fetchJSON(this[p.getURL](path), 'DELETE', body)); | ||
} | ||
|
||
/** | ||
* Performs either HTTP 'GET' or 'PUT' (if body parameter is specified) API | ||
* request and accepts Blob as response. | ||
* | ||
* @param {string} path Specific API resource path to be used in conjunction | ||
* with the base API path. | ||
* @param {Object=} body Optional object that will be serialized to JSON | ||
* string and sent as 'PUT' body. | ||
* @param {string=} accept Mime type of the Blob we expect as a response | ||
* (default is image/jpeg). | ||
* @return {Promise} | ||
*/ | ||
blob(path, body, accept = 'image/jpeg') { | ||
return this[p.onceReady]() | ||
.then(() => { | ||
if (body) { | ||
return this[p.net].fetchBlob( | ||
this[p.getURL](path), accept, 'PUT', body | ||
); | ||
} | ||
|
||
return this[p.net].fetchBlob(this[p.getURL](path), accept); | ||
}); | ||
} | ||
|
||
/** | ||
* Creates a fully qualified API URL based on predefined base origin, API | ||
* version and specified resource path. | ||
* | ||
* @param {string} path Specific API resource path to be used in conjunction | ||
* with the base API path and version. | ||
* @return {string} | ||
* @private | ||
*/ | ||
[p.getURL](path) { | ||
if (!path || typeof path !== 'string') { | ||
throw new Error('Path should be a valid non-empty string.'); | ||
} | ||
|
||
return `${this[p.net].origin}/api/v${this[p.settings].apiVersion}/${path}`; | ||
} | ||
|
||
/** | ||
* Returns a promise that is resolved once API is ready to use (API host is | ||
* online). | ||
* In the future we can add more checks like: | ||
* * User is authenticated | ||
* * Document is visible | ||
* | ||
* @returns {Promise} | ||
* @private | ||
*/ | ||
[p.onceReady]() { | ||
return Promise.all([ | ||
this[p.onceOnline](), | ||
]); | ||
} | ||
|
||
/** | ||
* Returns a promise that is resolved once API host is discovered and online. | ||
* | ||
* @returns {Promise} | ||
* @private | ||
*/ | ||
[p.onceOnline]() { | ||
const net = this[p.net]; | ||
if (net.online) { | ||
return Promise.resolve(); | ||
} | ||
|
||
return new Promise((resolve) => net.once('online', () => resolve())); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* global URLSearchParams */ | ||
|
||
'use strict'; | ||
|
||
import EventDispatcher from '../common/event-dispatcher'; | ||
|
||
import Settings from './settings'; | ||
import Network from './network'; | ||
import WebPush from './webpush'; | ||
import API from './api'; | ||
import Reminders from './reminders'; | ||
|
||
// Private members. | ||
const p = Object.freeze({ | ||
// Private properties. | ||
settings: Symbol('settings'), | ||
net: Symbol('net'), | ||
webPush: Symbol('webPush'), | ||
api: Symbol('api'), | ||
}); | ||
|
||
export default class Server extends EventDispatcher { | ||
constructor({ settings, net } = {}) { | ||
super(['online']); | ||
|
||
// Private properties. | ||
this[p.settings] = settings || new Settings(); | ||
this[p.net] = net || new Network(this[p.settings]); | ||
this[p.api] = new API(this[p.net], this[p.settings]); | ||
this[p.webPush] = new WebPush(this[p.api], this[p.settings]); | ||
|
||
// Init | ||
this.reminders = new Reminders(this[p.api], this[p.settings]); | ||
|
||
this[p.net].on('online', (online) => this.emit('online', online)); | ||
this[p.webPush].on('message', (msg) => this.emit('push-message', msg)); | ||
|
||
window.server = this; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add things to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is for hand debugging too. It should be stripped out on production builds. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can still use We don't currently differentiate between production/dev builds yet do we? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do, we replace some components code by their minified counterpart in production (React in particular does many unnecessary checks useful for dev but not in prod) and the code is not minified in dev. See in |
||
|
||
Object.seal(this); | ||
} | ||
|
||
/** | ||
* Clear all data/settings stored on the browser. Use with caution. | ||
* | ||
* @param {boolean} ignoreServiceWorker | ||
* @return {Promise} | ||
*/ | ||
clear(ignoreServiceWorker = true) { | ||
const promises = [this[p.settings].clear()]; | ||
|
||
if (!navigator.serviceWorker && !ignoreServiceWorker) { | ||
promises.push(navigator.serviceWorker.ready | ||
.then((registration) => registration.unregister())); | ||
} | ||
|
||
return Promise.all(promises); | ||
} | ||
|
||
get online() { | ||
return this[p.net].online; | ||
} | ||
|
||
get isLoggedIn() { | ||
return !!this[p.settings].session; | ||
} | ||
|
||
/** | ||
* Authenticate a user. | ||
* | ||
* @param {string} user | ||
* @param {string} password | ||
* @return {Promise} | ||
*/ | ||
login(user, password) { | ||
return this[p.api].post('login', { user, password }) | ||
.then((res) => { | ||
this[p.settings].session = res.token; | ||
}); | ||
} | ||
|
||
/** | ||
* Log out the user. | ||
* | ||
* @return {Promise} | ||
*/ | ||
logout() { | ||
this[p.settings].session = null; | ||
return Promise.resolve(); | ||
} | ||
|
||
/** | ||
* Ask the user to accept push notifications from the server. | ||
* This method will be called each time that we log in, but will stop the | ||
* execution if we already have the push subscription information. | ||
* | ||
* @param {boolean} resubscribe Parameter used for testing purposes, and | ||
* follow the whole subscription process even if we already have a push | ||
* subscription information. | ||
* @return {Promise} | ||
*/ | ||
subscribeToNotifications(resubscribe = false) { | ||
return this[p.webPush].subscribeToNotifications(resubscribe); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this pattern intended?On reflection it kind of makes sense :)settings
andnet
will be undefined by default.Ooh, TIL: in ES6 you can do this:
constructor({settings, net} = { settings: new Settings(), })
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is mainly used for testing purposes to inject mocks into the constructor.