frankly-js
Version:
JavaScript library providing building blocks to write applications using the Frankly API.
303 lines (255 loc) • 8.02 kB
JavaScript
/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Frankly Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
var _forEach = require('lodash/collection/forEach')
var _find = require('lodash/collection/find')
var Promise = require('promise')
var configs = []
var methods = {
configuration: function (request, source, origin) {
return new Promise(function (resolve, reject) {
var config = getConfigByWindowOrDefault(source)
if (config === undefined) {
// If there are no configuration available we place a callback
// in the list of pending windows waiting for a configuration
// item to become available.
configs.push({
ready: false,
window: source,
pending: [
function (config) {
resolve(cleanConfig(config))
},
]
})
return
} else if (!config.ready) {
config.pending.push(function (config) {
resolve(cleanConfig(config))
})
return
}
resolve(cleanConfig(config))
})
},
authentication: function (request, source, origin) {
return new Promise(function (resolve, reject) {
var config = getConfigByWindowOrDefault(source)
if (config === undefined) {
reject(new Error('No configuration was found for the requesting window'))
return
}
if (config.onAuthenticationRequest === undefined) {
reject(new Error('No authentication method available for the requesting window'))
return
}
console.log(request, request.data)
config.onAuthenticationRequest(request.data)
.then(resolve)
.catch(reject)
})
},
}
function success (result, request, source, origin) {
source.postMessage({
protocol: request.protocol,
id: request.id,
method: request.method,
type: 'response',
ok: true,
time: new Date,
data: result,
}, origin)
}
function failure (error, request, source, origin) {
try {
error = {
name: error.name,
message: error.message,
}
} catch (e) {
console.log(error)
error = {
name: e.name,
message: e.message,
}
}
source.postMessage({
protocol: request.protocol,
id: request.id,
method: request.method,
type: 'response',
ok: false,
time: new Date,
data: error,
}, origin)
}
function copyConfig (config) {
var cfg = config instanceof Array ? [] : { }
_forEach(config, function (val, key) {
if (val !== null && typeof val === 'object') {
throw new TypeError('unexpected object in configuration')
} else {
cfg[key] = val
}
})
return cfg
}
function cleanConfig (config) {
var cfg = config instanceof Array ? [] : { }
_forEach(config, function (val, key) {
if (val === null) {
cfg[key] = val
} else {
switch (typeof val) {
case 'undefined':
case 'boolean':
case 'number':
case 'string':
case 'symbol':
cfg[key] = val
break
case 'function':
cfg[key] = true
break
}
}
})
return cfg
}
function notifyConfigReady (config) {
var pending = config.pending
if (pending !== undefined) {
_forEach(pending, function (ready) {
try {
ready(config)
} catch (e) {
console.log(e)
}
})
}
}
function getConfigByWindow (window) {
return _find(configs, { window: window })
}
function getConfigByWindowOrDefault (window) {
var config = getConfigByWindow(window)
if (config === undefined) {
config = getConfigByWindow(undefined)
}
return config
}
window.addEventListener('message', function (event) {
var request = event.data
var source = event.source
var origin = event.origin
var method = undefined
if (request === undefined) {
console.log('frankly widgets received an invalid event (undefined request)')
return
}
if (source === undefined) {
console.log('frankly widgets received an invalid event (undinfed source)')
return
}
if (origin === undefined) {
console.log('frankly widgets received an invalid event (undefined origin)')
return
}
if (request.protocol !== 'frankly;v1') {
console.log('frankly widgets received an invalid request for protocol', request.protocol)
return
}
if (request.type !== 'request') {
return
}
method = methods[request.method]
if (method === undefined) {
console.log('frankly widgets received an invalid request for method', request.method)
return
}
method(request, source, origin)
.then(function (result) { success(result, request, source, origin) })
.catch(function (error) { failure(error, request, source, origin) })
})
module.exports = {
init: function (options) {
var element = undefined
var previous = undefined
var key = undefined
var pending = undefined
if (options === undefined) {
options = { }
} else {
options = copyConfig(options)
}
// Lookup the window associated with the provided id (if any).
if (options.id === undefined) {
options.window = undefined
} else {
element = document.getElementById(options.id)
if (element === undefined) {
throw new Error('no element with id ' + options.id + ' could be found')
}
if (element.contentWindow === undefined) {
throw new Error('the element with id ' + options.id + ' is not an iframe')
}
if (getConfigByWindow(undefined) !== undefined) {
throw new Error('the default configuration has already been set, no more calls to frankly.widgets.init can be made')
}
options.window = element.contentWindow
}
// Check if we had registered a previously configuration for that
// window, there are two cases where that may happen:
//
// - one of the windows has already loaded and is waiting for
// configuration, this is valid case and we'll complete it when
// we're done setting up the configuration.
//
// - the frankly.widgets.init function was called multiple times
// by the host page and we'll throw an error.
previous = getConfigByWindow(options.window)
if (previous === undefined) {
configs.push(options)
} else {
if (previous.ready) {
throw new Error('multiple calls to frankly.widgets.init for the same configuration were detected')
}
_forEach(options, function (val, key) {
previous[key] = val
})
options = previous
}
options.ready = true
if (options.window !== undefined) {
// If there are pending windows waiting for this configuration
// to become available we notify them all.
notifyConfigReady(options)
} else {
// If the default configuration is being set we notify pending
// windows for all configurations.
_forEach(configs, notifyConfigReady)
}
}
}