706 lines
18 KiB
JavaScript
706 lines
18 KiB
JavaScript
(function() {
|
|
var isIOS = (/iphone|ipad|ipod/i).test(window.navigator.userAgent.toLowerCase());
|
|
if (isIOS) {
|
|
console = {};
|
|
console.log = function(log) {
|
|
var iframe = document.createElement('iframe');
|
|
iframe.setAttribute('src', 'ios-log: ' + log);
|
|
document.documentElement.appendChild(iframe);
|
|
iframe.parentNode.removeChild(iframe);
|
|
iframe = null;
|
|
};
|
|
console.debug = console.info = console.warn = console.error = console.log;
|
|
}
|
|
}());
|
|
|
|
(function() {
|
|
var mraid = window.mraid = {};
|
|
|
|
var bridge = window.mraidbridge = {
|
|
nativeSDKFiredReady: false,
|
|
nativeCallQueue: [],
|
|
nativeCallInFlight: false,
|
|
lastSizeChangeProperties: null
|
|
};
|
|
|
|
bridge.fireChangeEvent = function(properties) {
|
|
for (var p in properties) {
|
|
if (properties.hasOwnProperty(p)) {
|
|
var handler = changeHandlers[p];
|
|
handler(properties[p]);
|
|
}
|
|
}
|
|
};
|
|
|
|
bridge.nativeCallComplete = function(command) {
|
|
if (this.nativeCallQueue.length === 0) {
|
|
this.nativeCallInFlight = false;
|
|
return;
|
|
}
|
|
|
|
var nextCall = this.nativeCallQueue.pop();
|
|
window.location = nextCall;
|
|
};
|
|
|
|
bridge.executeNativeCall = function(args) {
|
|
var command = args.shift();
|
|
|
|
if (!this.nativeSDKFiredReady) {
|
|
console.log('rejecting ' + command + ' because mraid is not ready');
|
|
bridge.notifyErrorEvent('mraid is not ready', command);
|
|
return;
|
|
}
|
|
|
|
var call = 'mraid://' + command;
|
|
|
|
var key, value;
|
|
var isFirstArgument = true;
|
|
|
|
for (var i = 0; i < args.length; i += 2) {
|
|
key = args[i];
|
|
value = args[i + 1];
|
|
|
|
if (value === null) continue;
|
|
|
|
if (isFirstArgument) {
|
|
call += '?';
|
|
isFirstArgument = false;
|
|
} else {
|
|
call += '&';
|
|
}
|
|
|
|
call += encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
|
}
|
|
|
|
if (this.nativeCallInFlight) {
|
|
this.nativeCallQueue.push(call);
|
|
} else {
|
|
this.nativeCallInFlight = true;
|
|
window.location = call;
|
|
}
|
|
};
|
|
|
|
bridge.setCurrentPosition = function(x, y, width, height) {
|
|
currentPosition = {
|
|
x: x,
|
|
y: y,
|
|
width: width,
|
|
height: height
|
|
};
|
|
broadcastEvent(EVENTS.INFO, 'Set current position to ' + stringify(currentPosition));
|
|
};
|
|
|
|
bridge.setDefaultPosition = function(x, y, width, height) {
|
|
defaultPosition = {
|
|
x: x,
|
|
y: y,
|
|
width: width,
|
|
height: height
|
|
};
|
|
broadcastEvent(EVENTS.INFO, 'Set default position to ' + stringify(defaultPosition));
|
|
};
|
|
|
|
bridge.setMaxSize = function(width, height) {
|
|
maxSize = {
|
|
width: width,
|
|
height: height
|
|
};
|
|
|
|
expandProperties.width = width;
|
|
expandProperties.height = height;
|
|
|
|
broadcastEvent(EVENTS.INFO, 'Set max size to ' + stringify(maxSize));
|
|
};
|
|
|
|
bridge.setPlacementType = function(_placementType) {
|
|
placementType = _placementType;
|
|
broadcastEvent(EVENTS.INFO, 'Set placement type to ' + stringify(placementType));
|
|
};
|
|
|
|
bridge.setScreenSize = function(width, height) {
|
|
screenSize = {
|
|
width: width,
|
|
height: height
|
|
};
|
|
broadcastEvent(EVENTS.INFO, 'Set screen size to ' + stringify(screenSize));
|
|
};
|
|
|
|
bridge.setState = function(_state) {
|
|
state = _state;
|
|
broadcastEvent(EVENTS.INFO, 'Set state to ' + stringify(state));
|
|
broadcastEvent(EVENTS.STATECHANGE, state);
|
|
};
|
|
|
|
bridge.setIsViewable = function(_isViewable) {
|
|
isViewable = _isViewable;
|
|
broadcastEvent(EVENTS.INFO, 'Set isViewable to ' + stringify(isViewable));
|
|
broadcastEvent(EVENTS.VIEWABLECHANGE, isViewable);
|
|
};
|
|
|
|
bridge.setSupports = function(sms, tel, calendar, storePicture, inlineVideo) {
|
|
supportProperties = {
|
|
sms: sms,
|
|
tel: tel,
|
|
calendar: calendar,
|
|
storePicture: storePicture,
|
|
inlineVideo: inlineVideo
|
|
};
|
|
};
|
|
|
|
bridge.notifyReadyEvent = function() {
|
|
this.nativeSDKFiredReady = true;
|
|
broadcastEvent(EVENTS.READY);
|
|
};
|
|
|
|
bridge.notifyErrorEvent = function(message, action) {
|
|
broadcastEvent(EVENTS.ERROR, message, action);
|
|
};
|
|
|
|
bridge.fireReadyEvent = bridge.notifyReadyEvent;
|
|
bridge.fireErrorEvent = bridge.notifyErrorEvent;
|
|
|
|
bridge.notifySizeChangeEvent = function(width, height) {
|
|
if (this.lastSizeChangeProperties &&
|
|
width == this.lastSizeChangeProperties.width && height == this.lastSizeChangeProperties.height) {
|
|
return;
|
|
}
|
|
|
|
this.lastSizeChangeProperties = {
|
|
width: width,
|
|
height: height
|
|
};
|
|
broadcastEvent(EVENTS.SIZECHANGE, width, height);
|
|
};
|
|
|
|
bridge.notifyStateChangeEvent = function() {
|
|
if (state === STATES.LOADING) {
|
|
broadcastEvent(EVENTS.INFO, 'Native SDK initialized.');
|
|
}
|
|
|
|
broadcastEvent(EVENTS.INFO, 'Set state to ' + stringify(state));
|
|
broadcastEvent(EVENTS.STATECHANGE, state);
|
|
};
|
|
|
|
bridge.notifyViewableChangeEvent = function() {
|
|
broadcastEvent(EVENTS.INFO, 'Set isViewable to ' + stringify(isViewable));
|
|
broadcastEvent(EVENTS.VIEWABLECHANGE, isViewable);
|
|
};
|
|
|
|
var VERSION = mraid.VERSION = '2.0';
|
|
|
|
var STATES = mraid.STATES = {
|
|
LOADING: 'loading',
|
|
DEFAULT: 'default',
|
|
EXPANDED: 'expanded',
|
|
HIDDEN: 'hidden',
|
|
RESIZED: 'resized'
|
|
};
|
|
|
|
var EVENTS = mraid.EVENTS = {
|
|
ERROR: 'error',
|
|
INFO: 'info',
|
|
READY: 'ready',
|
|
STATECHANGE: 'stateChange',
|
|
VIEWABLECHANGE: 'viewableChange',
|
|
SIZECHANGE: 'sizeChange'
|
|
};
|
|
|
|
var PLACEMENT_TYPES = mraid.PLACEMENT_TYPES = {
|
|
UNKNOWN: 'unknown',
|
|
INLINE: 'inline',
|
|
INTERSTITIAL: 'interstitial'
|
|
};
|
|
|
|
var expandProperties = {
|
|
width: false,
|
|
height: false,
|
|
useCustomClose: false,
|
|
isModal: true
|
|
};
|
|
|
|
var resizeProperties = {
|
|
width: false,
|
|
height: false,
|
|
offsetX: false,
|
|
offsetY: false,
|
|
customClosePosition: 'top-right',
|
|
allowOffscreen: true
|
|
};
|
|
|
|
var orientationProperties = {
|
|
allowOrientationChange: true,
|
|
forceOrientation: "none"
|
|
};
|
|
|
|
var supportProperties = {
|
|
sms: false,
|
|
tel: false,
|
|
calendar: false,
|
|
storePicture: false,
|
|
inlineVideo: false
|
|
};
|
|
|
|
var lastSizeChangeProperties;
|
|
|
|
var maxSize = {};
|
|
|
|
var currentPosition = {};
|
|
|
|
var defaultPosition = {};
|
|
|
|
var screenSize = {};
|
|
|
|
var hasSetCustomClose = false;
|
|
|
|
var listeners = {};
|
|
|
|
var state = STATES.LOADING;
|
|
|
|
var isViewable = false;
|
|
|
|
var placementType = PLACEMENT_TYPES.UNKNOWN;
|
|
|
|
var hostSDKVersion = {
|
|
'major': 0,
|
|
'minor': 0,
|
|
'patch': 0
|
|
};
|
|
|
|
var EventListeners = function(event) {
|
|
this.event = event;
|
|
this.count = 0;
|
|
var listeners = {};
|
|
|
|
this.add = function(func) {
|
|
var id = String(func);
|
|
if (!listeners[id]) {
|
|
listeners[id] = func;
|
|
this.count++;
|
|
}
|
|
};
|
|
|
|
this.remove = function(func) {
|
|
var id = String(func);
|
|
if (listeners[id]) {
|
|
listeners[id] = null;
|
|
delete listeners[id];
|
|
this.count--;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
this.removeAll = function() {
|
|
for (var id in listeners) {
|
|
if (listeners.hasOwnProperty(id)) this.remove(listeners[id]);
|
|
}
|
|
};
|
|
|
|
this.broadcast = function(args) {
|
|
for (var id in listeners) {
|
|
if (listeners.hasOwnProperty(id)) listeners[id].apply(mraid, args);
|
|
}
|
|
};
|
|
|
|
this.toString = function() {
|
|
var out = [event, ':'];
|
|
for (var id in listeners) {
|
|
if (listeners.hasOwnProperty(id)) out.push('|', id, '|');
|
|
}
|
|
return out.join('');
|
|
};
|
|
};
|
|
|
|
var broadcastEvent = function() {
|
|
var args = new Array(arguments.length);
|
|
var l = arguments.length;
|
|
for (var i = 0; i < l; i++) args[i] = arguments[i];
|
|
var event = args.shift();
|
|
if (listeners[event]) listeners[event].broadcast(args);
|
|
};
|
|
|
|
var contains = function(value, array) {
|
|
for (var i in array) {
|
|
if (array[i] === value) return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
var clone = function(obj) {
|
|
if (obj === null) return null;
|
|
var f = function() {};
|
|
f.prototype = obj;
|
|
return new f();
|
|
};
|
|
|
|
var stringify = function(obj) {
|
|
if (typeof obj === 'object') {
|
|
var out = [];
|
|
if (obj.push) {
|
|
|
|
for (var p in obj) out.push(obj[p]);
|
|
return '[' + out.join(',') + ']';
|
|
} else {
|
|
|
|
for (var p in obj) out.push("'" + p + "': " + obj[p]);
|
|
return '{' + out.join(',') + '}';
|
|
}
|
|
} else return String(obj);
|
|
};
|
|
|
|
var trim = function(str) {
|
|
return str.replace(/^\s+|\s+$/g, '');
|
|
};
|
|
|
|
var changeHandlers = {
|
|
state: function(val) {
|
|
if (state === STATES.LOADING) {
|
|
broadcastEvent(EVENTS.INFO, 'Native SDK initialized.');
|
|
}
|
|
state = val;
|
|
broadcastEvent(EVENTS.INFO, 'Set state to ' + stringify(val));
|
|
broadcastEvent(EVENTS.STATECHANGE, state);
|
|
},
|
|
|
|
viewable: function(val) {
|
|
isViewable = val;
|
|
broadcastEvent(EVENTS.INFO, 'Set isViewable to ' + stringify(val));
|
|
broadcastEvent(EVENTS.VIEWABLECHANGE, isViewable);
|
|
},
|
|
|
|
placementType: function(val) {
|
|
broadcastEvent(EVENTS.INFO, 'Set placementType to ' + stringify(val));
|
|
placementType = val;
|
|
},
|
|
|
|
sizeChange: function(val) {
|
|
broadcastEvent(EVENTS.INFO, 'Set screenSize to ' + stringify(val));
|
|
for (var key in val) {
|
|
if (val.hasOwnProperty(key)) screenSize[key] = val[key];
|
|
}
|
|
},
|
|
|
|
supports: function(val) {
|
|
broadcastEvent(EVENTS.INFO, 'Set supports to ' + stringify(val));
|
|
supportProperties = val;
|
|
},
|
|
|
|
hostSDKVersion: function(val) {
|
|
|
|
var versions = val.split('.').map(function(version) {
|
|
return parseInt(version, 10);
|
|
}).filter(function(version) {
|
|
return version >= 0;
|
|
});
|
|
|
|
if (versions.length >= 3) {
|
|
hostSDKVersion['major'] = parseInt(versions[0], 10);
|
|
hostSDKVersion['minor'] = parseInt(versions[1], 10);
|
|
hostSDKVersion['patch'] = parseInt(versions[2], 10);
|
|
broadcastEvent(EVENTS.INFO, 'Set hostSDKVersion to ' + stringify(hostSDKVersion));
|
|
}
|
|
}
|
|
};
|
|
|
|
var validate = function(obj, validators, action, merge) {
|
|
if (!merge) {
|
|
|
|
if (obj === null) {
|
|
broadcastEvent(EVENTS.ERROR, 'Required object not provided.', action);
|
|
return false;
|
|
} else {
|
|
for (var i in validators) {
|
|
if (validators.hasOwnProperty(i) && obj[i] === undefined) {
|
|
broadcastEvent(EVENTS.ERROR, 'Object is missing required property: ' + i, action);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var prop in obj) {
|
|
var validator = validators[prop];
|
|
var value = obj[prop];
|
|
if (validator && !validator(value)) {
|
|
|
|
broadcastEvent(EVENTS.ERROR, 'Value of property ' + prop + ' is invalid: ' + value, action);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
var expandPropertyValidators = {
|
|
useCustomClose: function(v) {
|
|
return (typeof v === 'boolean');
|
|
},
|
|
};
|
|
|
|
mraid.addEventListener = function(event, listener) {
|
|
if (!event || !listener) {
|
|
broadcastEvent(EVENTS.ERROR, 'Both event and listener are required.', 'addEventListener');
|
|
} else if (!contains(event, EVENTS)) {
|
|
broadcastEvent(EVENTS.ERROR, 'Unknown MRAID event: ' + event, 'addEventListener');
|
|
} else {
|
|
if (!listeners[event]) {
|
|
listeners[event] = new EventListeners(event);
|
|
}
|
|
listeners[event].add(listener);
|
|
}
|
|
};
|
|
|
|
mraid.close = function() {
|
|
if (state === STATES.HIDDEN) {
|
|
broadcastEvent(EVENTS.ERROR, 'Ad cannot be closed when it is already hidden.',
|
|
'close');
|
|
} else bridge.executeNativeCall(['close']);
|
|
};
|
|
|
|
mraid.expand = function(URL) {
|
|
if (!(this.getState() === STATES.DEFAULT || this.getState() === STATES.RESIZED)) {
|
|
broadcastEvent(EVENTS.ERROR, 'Ad can only be expanded from the default or resized state.', 'expand');
|
|
} else {
|
|
var args = ['expand',
|
|
'shouldUseCustomClose', expandProperties.useCustomClose
|
|
];
|
|
|
|
if (URL) {
|
|
args = args.concat(['url', URL]);
|
|
}
|
|
|
|
bridge.executeNativeCall(args);
|
|
}
|
|
};
|
|
|
|
mraid.getExpandProperties = function() {
|
|
var properties = {
|
|
width: expandProperties.width,
|
|
height: expandProperties.height,
|
|
useCustomClose: expandProperties.useCustomClose,
|
|
isModal: expandProperties.isModal
|
|
};
|
|
return properties;
|
|
};
|
|
|
|
mraid.getCurrentPosition = function() {
|
|
return {
|
|
x: currentPosition.x,
|
|
y: currentPosition.y,
|
|
width: currentPosition.width,
|
|
height: currentPosition.height
|
|
};
|
|
};
|
|
|
|
mraid.getDefaultPosition = function() {
|
|
return {
|
|
x: defaultPosition.x,
|
|
y: defaultPosition.y,
|
|
width: defaultPosition.width,
|
|
height: defaultPosition.height
|
|
};
|
|
};
|
|
|
|
mraid.getMaxSize = function() {
|
|
return {
|
|
width: maxSize.width,
|
|
height: maxSize.height
|
|
};
|
|
};
|
|
|
|
mraid.getPlacementType = function() {
|
|
return placementType;
|
|
};
|
|
|
|
mraid.getScreenSize = function() {
|
|
return {
|
|
width: screenSize.width,
|
|
height: screenSize.height
|
|
};
|
|
};
|
|
|
|
mraid.getState = function() {
|
|
return state;
|
|
};
|
|
|
|
mraid.isViewable = function() {
|
|
return isViewable;
|
|
};
|
|
|
|
mraid.getVersion = function() {
|
|
return mraid.VERSION;
|
|
};
|
|
|
|
mraid.open = function(URL) {
|
|
if (!URL) broadcastEvent(EVENTS.ERROR, 'URL is required.', 'open');
|
|
else bridge.executeNativeCall(['open', 'url', URL]);
|
|
};
|
|
|
|
mraid.removeEventListener = function(event, listener) {
|
|
if (!event) {
|
|
broadcastEvent(EVENTS.ERROR, 'Event is required.', 'removeEventListener');
|
|
return;
|
|
}
|
|
|
|
if (listener) {
|
|
var success = false;
|
|
if (listeners[event]) {
|
|
success = listeners[event].remove(listener);
|
|
}
|
|
|
|
if (!success) {
|
|
broadcastEvent(EVENTS.ERROR, 'Listener not currently registered for event.', 'removeEventListener');
|
|
return;
|
|
}
|
|
|
|
} else if (!listener && listeners[event]) {
|
|
listeners[event].removeAll();
|
|
}
|
|
|
|
if (listeners[event] && listeners[event].count === 0) {
|
|
listeners[event] = null;
|
|
delete listeners[event];
|
|
}
|
|
};
|
|
|
|
mraid.setExpandProperties = function(properties) {
|
|
if (validate(properties, expandPropertyValidators, 'setExpandProperties', true)) {
|
|
if (properties.hasOwnProperty('useCustomClose')) {
|
|
expandProperties.useCustomClose = properties.useCustomClose;
|
|
}
|
|
}
|
|
};
|
|
|
|
mraid.useCustomClose = function(shouldUseCustomClose) {
|
|
expandProperties.useCustomClose = shouldUseCustomClose;
|
|
hasSetCustomClose = true;
|
|
bridge.executeNativeCall(['usecustomclose', 'shouldUseCustomClose', shouldUseCustomClose]);
|
|
};
|
|
|
|
mraid.supports = function(feature) {
|
|
return supportProperties[feature];
|
|
};
|
|
|
|
mraid.playVideo = function(uri) {
|
|
if (!mraid.isViewable()) {
|
|
broadcastEvent(EVENTS.ERROR, 'playVideo cannot be called until the ad is viewable', 'playVideo');
|
|
return;
|
|
}
|
|
|
|
if (!uri) {
|
|
broadcastEvent(EVENTS.ERROR, 'playVideo must be called with a valid URI', 'playVideo');
|
|
} else {
|
|
bridge.executeNativeCall(['playVideo', 'uri', uri]);
|
|
}
|
|
};
|
|
|
|
mraid.storePicture = function(uri) {
|
|
if (!mraid.isViewable()) {
|
|
broadcastEvent(EVENTS.ERROR, 'storePicture cannot be called until the ad is viewable', 'storePicture');
|
|
return;
|
|
}
|
|
|
|
if (!uri) {
|
|
broadcastEvent(EVENTS.ERROR, 'storePicture must be called with a valid URI', 'storePicture');
|
|
} else {
|
|
bridge.executeNativeCall(['storePicture', 'uri', uri]);
|
|
}
|
|
};
|
|
|
|
var resizePropertyValidators = {
|
|
width: function(v) {
|
|
return !isNaN(v) && v > 0;
|
|
},
|
|
height: function(v) {
|
|
return !isNaN(v) && v > 0;
|
|
},
|
|
offsetX: function(v) {
|
|
return !isNaN(v);
|
|
},
|
|
offsetY: function(v) {
|
|
return !isNaN(v);
|
|
},
|
|
customClosePosition: function(v) {
|
|
return (typeof v === 'string' && ['top-right', 'bottom-right', 'top-left', 'bottom-left', 'center', 'top-center', 'bottom-center'].indexOf(v) > -1);
|
|
},
|
|
allowOffscreen: function(v) {
|
|
return (typeof v === 'boolean');
|
|
}
|
|
};
|
|
|
|
mraid.setOrientationProperties = function(properties) {
|
|
|
|
if (properties.hasOwnProperty('allowOrientationChange')) {
|
|
orientationProperties.allowOrientationChange = properties.allowOrientationChange;
|
|
}
|
|
|
|
if (properties.hasOwnProperty('forceOrientation')) {
|
|
orientationProperties.forceOrientation = properties.forceOrientation;
|
|
}
|
|
|
|
var args = ['setOrientationProperties',
|
|
'allowOrientationChange', orientationProperties.allowOrientationChange,
|
|
'forceOrientation', orientationProperties.forceOrientation
|
|
];
|
|
bridge.executeNativeCall(args);
|
|
};
|
|
|
|
mraid.getOrientationProperties = function() {
|
|
return {
|
|
allowOrientationChange: orientationProperties.allowOrientationChange,
|
|
forceOrientation: orientationProperties.forceOrientation
|
|
};
|
|
};
|
|
|
|
mraid.resize = function() {
|
|
if (!(this.getState() === STATES.DEFAULT || this.getState() === STATES.RESIZED)) {
|
|
broadcastEvent(EVENTS.ERROR, 'Ad can only be resized from the default or resized state.', 'resize');
|
|
} else if (!resizeProperties.width || !resizeProperties.height) {
|
|
broadcastEvent(EVENTS.ERROR, 'Must set resize properties before calling resize()', 'resize');
|
|
} else {
|
|
var args = ['resize',
|
|
'width', resizeProperties.width,
|
|
'height', resizeProperties.height,
|
|
'offsetX', resizeProperties.offsetX || 0,
|
|
'offsetY', resizeProperties.offsetY || 0,
|
|
'customClosePosition', resizeProperties.customClosePosition,
|
|
'allowOffscreen', !!resizeProperties.allowOffscreen
|
|
];
|
|
|
|
bridge.executeNativeCall(args);
|
|
}
|
|
};
|
|
|
|
mraid.getResizeProperties = function() {
|
|
var properties = {
|
|
width: resizeProperties.width,
|
|
height: resizeProperties.height,
|
|
offsetX: resizeProperties.offsetX,
|
|
offsetY: resizeProperties.offsetY,
|
|
customClosePosition: resizeProperties.customClosePosition,
|
|
allowOffscreen: resizeProperties.allowOffscreen
|
|
};
|
|
return properties;
|
|
};
|
|
|
|
mraid.setResizeProperties = function(properties) {
|
|
if (validate(properties, resizePropertyValidators, 'setResizeProperties', true)) {
|
|
|
|
var desiredProperties = ['width', 'height', 'offsetX', 'offsetY', 'customClosePosition', 'allowOffscreen'];
|
|
|
|
var length = desiredProperties.length;
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
var propname = desiredProperties[i];
|
|
if (properties.hasOwnProperty(propname)) {
|
|
resizeProperties[propname] = properties[propname];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
mraid.getHostSDKVersion = function() {
|
|
return hostSDKVersion;
|
|
}
|
|
|
|
}()); |