Comparing version 0.2.0 to 0.3.0
@@ -15,4 +15,3 @@ (function (global, factory) { | ||
methods: {}, | ||
properties: {}, | ||
events: {} // TODO | ||
properties: {} | ||
}, | ||
@@ -22,4 +21,3 @@ SourceBuffer: { | ||
methods: {}, | ||
properties: {}, | ||
events: {} // TODO | ||
properties: {} | ||
} | ||
@@ -47,3 +45,3 @@ }; | ||
/** | ||
* Define the logger for startMSESpy. | ||
* Define the logger for the MSE-spy. | ||
* Allows to re-define a specific logger on runtime / before applying this | ||
@@ -53,45 +51,93 @@ * script. | ||
*/ | ||
var Logger = window.Logger || { | ||
var Logger = window.MSESpyLogger || { | ||
/* eslint-disable no-console */ | ||
log: function log() { | ||
var _console; | ||
(_console = console).log.apply(_console, arguments); | ||
/** | ||
* Triggered each time a property is accessed. | ||
* @param {string} pathString - human-readable path to the property. | ||
* @param {*} value - the value it currently has. | ||
*/ | ||
onPropertyAccess: function onPropertyAccess(pathString, value) { | ||
console.debug(">>> Getting ${pathString}:", value); | ||
}, | ||
debug: function debug() { | ||
var _console2; | ||
(_console2 = console).debug.apply(_console2, arguments); | ||
/** | ||
* Triggered each time a property is set. | ||
* @param {string} pathString - human-readable path to the property. | ||
* @param {*} value - the value it is set to. | ||
*/ | ||
onSettingProperty: function onSettingProperty(pathString, value) { | ||
console.debug(">> Setting " + pathString + ":", value); | ||
}, | ||
info: function info() { | ||
var _console3; | ||
(_console3 = console).info.apply(_console3, arguments); | ||
/** | ||
* Triggered when some object is instanciated (just before). | ||
* @param {string} objectName - human-readable name for the concerned object. | ||
* @param {Array.<*>} args - Arguments given to the constructor | ||
*/ | ||
onObjectInstanciation: function onObjectInstanciation(objectName, args) { | ||
if (args.length) { | ||
console.debug(">>> Creating ${objectName} with arguments:", args); | ||
} else { | ||
console.debug(">>> Creating ${objectName}"); | ||
} | ||
}, | ||
error: function error() { | ||
var _console4; | ||
(_console4 = console).error.apply(_console4, arguments); | ||
/** | ||
* Triggered when an Object instanciation failed. | ||
* @param {string} objectName - human-readable name for the concerned object. | ||
* @param {Error} error - Error thrown by the constructor | ||
*/ | ||
onObjectInstanciationError: function onObjectInstanciationError(objectName, error) { | ||
console.error(">> ${objectName} creation failed:", error); | ||
}, | ||
warning: function warning() { | ||
var _console5; | ||
(_console5 = console).warning.apply(_console5, arguments); | ||
/** | ||
* Triggered when an Object instanciation succeeded. | ||
* @param {string} objectName - human-readable name for the concerned object. | ||
* @param {*} value - The corresponding object instanciated. | ||
*/ | ||
onObjectInstanciationSuccess: function onObjectInstanciationSuccess(objectName, value) { | ||
console.debug(">>> ${objectName} created:", value); | ||
}, | ||
/** | ||
* Triggered when some method/function is called. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {Array.<*>} args - Arguments given to this function. | ||
*/ | ||
onFunctionCall: function onFunctionCall(pathName, args) { | ||
if (args.length) { | ||
console.debug(">>> " + pathName + " called with arguments:", args); | ||
} else { | ||
console.debug(">>> " + pathName + " called"); | ||
} | ||
}, | ||
/** | ||
* Triggered when a function call fails. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {Error} error - Error thrown by the call | ||
*/ | ||
onFunctionCallError: function onFunctionCallError(pathName, error) { | ||
console.error(">> " + pathName + " failed:", error); | ||
}, | ||
/** | ||
* Triggered when a function call succeeded. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {*} value - The result of the function | ||
*/ | ||
onFunctionCallSuccess: function onFunctionCallSuccess(pathName, value) { | ||
console.info(">>> ${pathName} succeeded:", value); | ||
} | ||
/* eslint-enable no-console */ | ||
}; | ||
/** | ||
* Log when a function is called with its arguments. | ||
* @param {string} fnName | ||
* @param {Array.<*>} args | ||
*/ | ||
function onAPICall(fnName, args) { | ||
if (args.length) { | ||
Logger.debug(">>> " + fnName + " called with arguments:", args); | ||
} else { | ||
Logger.debug(">>> " + fnName + " called"); | ||
} | ||
} | ||
function stubRegularMethods(obj, methods, path, logObj) { | ||
@@ -112,3 +158,3 @@ var _loop = function _loop(i) { | ||
onAPICall(completePath, args); | ||
Logger.onFunctionCall(completePath, args); | ||
var myObj = { | ||
@@ -129,3 +175,3 @@ self: obj, | ||
} catch (e) { | ||
Logger.error(">> " + completePath + " failed:", e); | ||
Logger.onFunctionCallError(completePath, e); | ||
myObj.error = e; | ||
@@ -135,3 +181,3 @@ myObj.errorDate = Date.now(); | ||
} | ||
Logger.debug(">> " + completePath + " succeeded:", res); | ||
Logger.onFunctionCallSuccess(completePath, res); | ||
myObj.response = res; | ||
@@ -160,4 +206,4 @@ myObj.responseDate = Date.now(); | ||
get: function get() { | ||
Logger.onPropertyAccess(completePath, value); | ||
var value = oldDescriptor.get.bind(this)(); | ||
var myObj = { | ||
@@ -168,3 +214,2 @@ self: this, | ||
}; | ||
if (!logObj[propertyName]) { | ||
@@ -176,4 +221,2 @@ logObj[propertyName] = { | ||
logObj[propertyName].get.push(myObj); | ||
Logger.debug(">> Getting " + completePath + ":", value); | ||
return value; | ||
@@ -201,4 +244,4 @@ } | ||
get: function get() { | ||
Logger.onPropertyAccess(completePath, value); | ||
var value = oldDescriptor.get.bind(this)(); | ||
var myObj = { | ||
@@ -218,8 +261,6 @@ self: this, | ||
Logger.debug(">> Getting " + completePath + ":", value); | ||
return value; | ||
}, | ||
set: function set(value) { | ||
Logger.debug(">> Setting " + completePath + ":", value); | ||
Logger.onSettingProperty(completePath, value); | ||
var myObj = { | ||
@@ -248,5 +289,19 @@ self: this, | ||
var MEDIASOURCE_SPY_OBJECT = { | ||
readOnlyProperties: ["sourceBuffers", "activeSourceBuffers", "readyState"], | ||
properties: ["duration", "onsourceopen", "onsourceended", "onsourceclose"], | ||
staticMethods: ["isTypeSupported"], | ||
methods: ["addEventListener", "removeEventListener", "dispatchEvent", "addSourceBuffer", "removeSourceBuffer", "endOfStream", "setLiveSeekableRange", "clearLiveSeekableRange"] | ||
}; | ||
var NativeMediaSourceProtoDescriptors = Object.getOwnPropertyDescriptors(NativeMediaSource.prototype); | ||
var NativeMediaSourceIsTypeSupported = NativeMediaSource.isTypeSupported; | ||
var NativeMediaSourceStaticMethods = MEDIASOURCE_SPY_OBJECT.staticMethods.reduce(function (acc, methodName) { | ||
acc[methodName] = NativeMediaSource[methodName]; | ||
return acc; | ||
}, {}); | ||
var NativeMediaSourceMethods = MEDIASOURCE_SPY_OBJECT.methods.reduce(function (acc, methodName) { | ||
acc[methodName] = NativeMediaSource.prototype[methodName]; | ||
return acc; | ||
}, {}); | ||
@@ -258,13 +313,21 @@ function StubbedMediaSource() { | ||
if (args.length) { | ||
Logger.debug(">>> Creating MediaSource with arguments:", args); | ||
} else { | ||
Logger.debug(">>> Creating MediaSource"); | ||
Logger.onObjectInstanciation("MediaSource", args); | ||
var now = Date.now(); | ||
var spyObj = { | ||
date: now, | ||
args: args | ||
}; | ||
MSE_CALLS.MediaSource.new.push(spyObj); | ||
var nativeMediaSource = void 0; | ||
try { | ||
nativeMediaSource = new (Function.prototype.bind.apply(NativeMediaSource, [null].concat(args)))(); | ||
} catch (e) { | ||
Logger.onObjectInstanciationError("MediaSource", e); | ||
spyObj.error = e; | ||
spyObj.errorDate = Date.now(); | ||
throw e; | ||
} | ||
var nativeMediaSource = new (Function.prototype.bind.apply(NativeMediaSource, [null].concat(args)))(); | ||
Logger.debug(">>> MediaSource created:", nativeMediaSource); | ||
stubReadOnlyProperties(nativeMediaSource, NativeMediaSourceProtoDescriptors, ["sourceBuffers", "activeSourceBuffers", "readyState"], "MediaSource.prototype", MSE_CALLS.MediaSource.properties); | ||
stubProperties(nativeMediaSource, NativeMediaSourceProtoDescriptors, ["duration", "onsourceopen", "onsourceended", "onsourceclose"], "MediaSource.prototype", MSE_CALLS.MediaSource.properties); | ||
stubRegularMethods(nativeMediaSource, ["addEventListener", "removeEventListener", "dispatchEvent", "addSourceBuffer", "removeSourceBuffer", "endOfStream", "setLiveSeekableRange", "clearLiveSeekableRange"], "MediaSource.prototype", MSE_CALLS.MediaSource.methods); | ||
Logger.onObjectInstanciationSuccess("MediaSource", nativeMediaSource); | ||
spyObj.response = nativeMediaSource; | ||
spyObj.responseDate = Date.now(); | ||
return nativeMediaSource; | ||
@@ -274,3 +337,6 @@ } | ||
function spyOnMediaSource() { | ||
stubRegularMethods(NativeMediaSource, ["isTypeSupported"], "MediaSource.isTypeSupported", MSE_CALLS.MediaSource.methods); | ||
stubReadOnlyProperties(NativeMediaSource.prototype, NativeMediaSourceProtoDescriptors, MEDIASOURCE_SPY_OBJECT.readOnlyProperties, "MediaSource.prototype", MSE_CALLS.MediaSource.properties); | ||
stubRegularMethods(NativeMediaSource, MEDIASOURCE_SPY_OBJECT.staticMethods, "MediaSource", MSE_CALLS.MediaSource.methods); | ||
stubProperties(NativeMediaSource.prototype, NativeMediaSourceProtoDescriptors, MEDIASOURCE_SPY_OBJECT.properties, "MediaSource.prototype", MSE_CALLS.MediaSource.properties); | ||
stubRegularMethods(NativeMediaSource.prototype, MEDIASOURCE_SPY_OBJECT.methods, "MediaSource.prototype", MSE_CALLS.MediaSource.methods); | ||
window.MediaSource = StubbedMediaSource; | ||
@@ -280,41 +346,77 @@ } | ||
function stopSpyingOnMediaSource() { | ||
Object.defineProperties(NativeMediaSource.prototype, MEDIASOURCE_SPY_OBJECT.properties.concat(MEDIASOURCE_SPY_OBJECT.readOnlyProperties).reduce(function (acc, propertyName) { | ||
acc[propertyName] = NativeMediaSourceProtoDescriptors[propertyName]; | ||
}, {})); | ||
MEDIASOURCE_SPY_OBJECT.staticMethods.forEach(function (methodName) { | ||
NativeMediaSource[methodName] = NativeMediaSourceStaticMethods[methodName]; | ||
}); | ||
MEDIASOURCE_SPY_OBJECT.methods.forEach(function (methodName) { | ||
NativeMediaSource.prototype[methodName] = NativeMediaSourceMethods[methodName]; | ||
}); | ||
window.MediaSource = NativeMediaSource; | ||
window.MediaSource.isTypeSupported = NativeMediaSourceIsTypeSupported; | ||
} | ||
var SOURCEBUFFER_SPY_OBJECT = { | ||
readOnlyProperties: ["updating", "buffered"], | ||
properties: ["mode", "timestampOffset", "appendWindowStart", "appendWindowEnd", "onupdate", "onupdatestart", "onupdateend", "onerror", "onabort"], | ||
staticMethods: [], | ||
methods: ["addEventListener", "removeEventListener", "dispatchEvent", "appendBuffer", "abort", "remove"] | ||
}; | ||
var NativeSourceBufferProtoDescriptors = Object.getOwnPropertyDescriptors(NativeSourceBuffer.prototype); | ||
var NativeSourceBufferAddEventListener = NativeSourceBuffer.prototype.addEventListener; | ||
var NativeSourceBufferRemoveEventListener = NativeSourceBuffer.prototype.removeEventListener; | ||
var NativeSourceBufferDispatchEvent = NativeSourceBuffer.prototype.dispatchEvent; | ||
var NativeSourceBufferAppendBuffer = NativeSourceBuffer.prototype.appendBuffer; | ||
var NativeSourceBufferAbort = NativeSourceBuffer.prototype.abort; | ||
var NativeSourceBufferRemove = NativeSourceBuffer.prototype.remove; | ||
var NativeSourceBufferStaticMethods = SOURCEBUFFER_SPY_OBJECT.staticMethods.reduce(function (acc, methodName) { | ||
acc[methodName] = NativeSourceBuffer[methodName]; | ||
return acc; | ||
}, {}); | ||
var NativeSourceBufferMethods = SOURCEBUFFER_SPY_OBJECT.methods.reduce(function (acc, methodName) { | ||
acc[methodName] = NativeSourceBuffer.prototype[methodName]; | ||
return acc; | ||
}, {}); | ||
function StubbedSourceBuffer() { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
Logger.onObjectInstanciation("SourceBuffer", args); | ||
var now = Date.now(); | ||
var spyObj = { | ||
date: now, | ||
args: args | ||
}; | ||
MSE_CALLS.SourceBuffer.new.push(spyObj); | ||
var nativeSourceBuffer = void 0; | ||
try { | ||
nativeSourceBuffer = new (Function.prototype.bind.apply(NativeSourceBuffer, [null].concat(args)))(); | ||
} catch (e) { | ||
Logger.onObjectInstanciationError("SourceBuffer", e); | ||
spyObj.error = e; | ||
spyObj.errorDate = Date.now(); | ||
throw e; | ||
} | ||
Logger.onObjectInstanciationSuccess("SourceBuffer", nativeSourceBuffer); | ||
spyObj.response = nativeSourceBuffer; | ||
spyObj.responseDate = Date.now(); | ||
return nativeSourceBuffer; | ||
} | ||
function spyOnSourceBuffer() { | ||
stubReadOnlyProperties(NativeSourceBuffer.prototype, NativeSourceBufferProtoDescriptors, ["updating", "buffered"], "SourceBuffer.prototype", MSE_CALLS.SourceBuffer.properties); | ||
stubProperties(NativeSourceBuffer.prototype, NativeSourceBufferProtoDescriptors, ["mode", "timestampOffset", "appendWindowStart", "appendWindowEnd", "onupdate", "onupdatestart", "onupdateend", "onerror", "onabort"], "SourceBuffer.prototype", MSE_CALLS.SourceBuffer.properties); | ||
stubRegularMethods(NativeSourceBuffer.prototype, ["addEventListener", "removeEventListener", "dispatchEvent", "appendBuffer", "abort", "remove"], "SourceBuffer.prototype", MSE_CALLS.SourceBuffer.methods); | ||
stubReadOnlyProperties(NativeSourceBuffer.prototype, NativeSourceBufferProtoDescriptors, SOURCEBUFFER_SPY_OBJECT.readOnlyProperties, "SourceBuffer.prototype", MSE_CALLS.SourceBuffer.properties); | ||
stubProperties(NativeSourceBuffer.prototype, NativeSourceBufferProtoDescriptors, SOURCEBUFFER_SPY_OBJECT.properties, "SourceBuffer.prototype", MSE_CALLS.SourceBuffer.properties); | ||
stubRegularMethods(NativeSourceBuffer.prototype, SOURCEBUFFER_SPY_OBJECT.methods, "SourceBuffer.prototype", MSE_CALLS.SourceBuffer.methods); | ||
window.SourceBuffer = StubbedSourceBuffer; | ||
} | ||
function stopSpyingOnSourceBuffer() { | ||
Object.defineProperties(NativeSourceBuffer.prototype, { | ||
updating: NativeSourceBufferProtoDescriptors.updating, | ||
buffered: NativeSourceBufferProtoDescriptors.buffered, | ||
mode: NativeSourceBufferProtoDescriptors.mode, | ||
timestampOffset: NativeSourceBufferProtoDescriptors.timestampOffset, | ||
appendWindowStart: NativeSourceBufferProtoDescriptors.appendWindowStart, | ||
appendWindowEnd: NativeSourceBufferProtoDescriptors.appendWindowEnd, | ||
onupdate: NativeSourceBufferProtoDescriptors.onupdate, | ||
onupdatestart: NativeSourceBufferProtoDescriptors.onupdatestart, | ||
onupdateend: NativeSourceBufferProtoDescriptors.onupdateend, | ||
onerror: NativeSourceBufferProtoDescriptors.onerror, | ||
onabort: NativeSourceBufferProtoDescriptors.onabort | ||
Object.defineProperties(NativeSourceBuffer.prototype, SOURCEBUFFER_SPY_OBJECT.properties.concat(SOURCEBUFFER_SPY_OBJECT.readOnlyProperties).reduce(function (acc, propertyName) { | ||
acc[propertyName] = NativeSourceBufferProtoDescriptors[propertyName]; | ||
}, {})); | ||
SOURCEBUFFER_SPY_OBJECT.staticMethods.forEach(function (methodName) { | ||
NativeSourceBuffer[methodName] = NativeSourceBufferStaticMethods[methodName]; | ||
}); | ||
NativeSourceBuffer.prototype.addEventListener = NativeSourceBufferAddEventListener; | ||
NativeSourceBuffer.prototype.removeEventListener = NativeSourceBufferRemoveEventListener; | ||
NativeSourceBuffer.prototype.dispatchEvent = NativeSourceBufferDispatchEvent; | ||
NativeSourceBuffer.prototype.appendBuffer = NativeSourceBufferAppendBuffer; | ||
NativeSourceBuffer.prototype.abort = NativeSourceBufferAbort; | ||
NativeSourceBuffer.prototype.remove = NativeSourceBufferRemove; | ||
SOURCEBUFFER_SPY_OBJECT.methods.forEach(function (methodName) { | ||
NativeSourceBuffer.prototype[methodName] = NativeSourceBufferMethods[methodName]; | ||
}); | ||
window.SourceBuffer = NativeSourceBuffer; | ||
} | ||
@@ -325,3 +427,3 @@ | ||
*/ | ||
function activateMSESpy() { | ||
function start() { | ||
spyOnMediaSource(); | ||
@@ -331,3 +433,3 @@ spyOnSourceBuffer(); | ||
function deactivateMSESpy() { | ||
function stop() { | ||
stopSpyingOnMediaSource(); | ||
@@ -340,4 +442,4 @@ stopSpyingOnSourceBuffer(); | ||
exports.Logger = Logger; | ||
exports.activateMSESpy = activateMSESpy; | ||
exports.deactivateMSESpy = deactivateMSESpy; | ||
exports.start = start; | ||
exports.stop = stop; | ||
@@ -344,0 +446,0 @@ Object.defineProperty(exports, '__esModule', { value: true }); |
{ | ||
"name": "mse-spy", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Report every MSE API a webpage calls", | ||
@@ -5,0 +5,0 @@ "main": "dist/bundle.js", |
205
README.md
@@ -6,7 +6,11 @@ # MSESpy.js #################################################################### | ||
This is a tool to spy on most MSE-related browser API calls. | ||
This is a tool to spy on most MSE-related browser API calls. It mainly has been | ||
used for debugging and reverse-engineering purposes on media-oriented | ||
web-applications. | ||
It logs and registers when any of the following actions take place: | ||
1. the following MediaSource methods are called: | ||
1. A MediaSource object is instanciated | ||
2. the following MediaSource methods are called: | ||
- ``MediaSource.prototype.addSourceBuffer`` | ||
@@ -22,3 +26,3 @@ - ``MediaSource.prototype.removeSourceBuffer`` | ||
2. Those MediaSource properties are get/set: | ||
3. Those MediaSource properties are get/set: | ||
- ``MediaSource.prototype.duration`` | ||
@@ -32,3 +36,3 @@ - ``MediaSource.prototype.onsourceopen`` | ||
3. Those SourceBuffer methods are called: | ||
4. Those SourceBuffer methods are called: | ||
- ``SourceBuffer.prototype.appendBuffer`` | ||
@@ -42,3 +46,3 @@ - ``SourceBuffer.prototype.abort`` | ||
4. Those SourceBuffer properties are get/set: | ||
5. Those SourceBuffer properties are get/set: | ||
- ``SourceBuffer.prototype.mode`` | ||
@@ -72,3 +76,3 @@ - ``SourceBuffer.prototype.timestampOffset`` | ||
It can then be used to produced useful reports on how those APIs are exploited | ||
by the application. | ||
by any application. | ||
@@ -91,5 +95,6 @@ | ||
// Start the spy | ||
MSESpy.activateMSESpy(); | ||
MSESpy.start(); | ||
// Get the global MSECalls object registering every calls | ||
// (More informations on it in the concerned chapter) | ||
const MSECalls = MSESpy.getMSECalls(); | ||
@@ -114,3 +119,3 @@ ``` | ||
// - will add entries to the global MSECalls object. | ||
MSESpy.activateMSESpy(); | ||
MSESpy.start(); | ||
@@ -121,2 +126,3 @@ // Get the global MSECalls object. | ||
// errors...) when the spy has been active. | ||
// (More informations on it in the concerned chapter) | ||
console.log(MSESpy.getMSECalls()); | ||
@@ -132,19 +138,182 @@ | ||
// - clean up the resources taken | ||
MSESpy.deactivateMSESpy(); | ||
MSESpy.stop(); | ||
// Spy again (after deactivating it) | ||
MSESpy.activateMSESpy(); | ||
MSESpy.start(); | ||
// You can also declare custom log functions | ||
// (More informations on it in the concerned chapter) | ||
MSESpy.Logger.debug = CustomLogger; | ||
``` | ||
// For debug calls (when a MSE API is called and was resolved/returned | ||
// sucessfully - depending on the type of API and when a property is accesed) | ||
MSESpy.Logger.debug = function(...args) { | ||
myPersonalLogger.debug(...args); | ||
} | ||
// For errors and rejected promises from MSE APIs | ||
MSESpy.Logger.error = function(...args) { | ||
myPersonalLogger.error(...args); | ||
### MSECalls object ############################################################ | ||
The MSECalls object contains information about every call performed while the | ||
spy was active. | ||
It can be obtained by calling the ``MSESpy.getMSECalls()`` API. | ||
Here is its basic structure: | ||
```js | ||
// MSE Calls object | ||
{ | ||
MediaSource: { // Data about the MediaSource native object | ||
new: [ // An entry is added each time a MediaSource is created. | ||
// Empty array by default. | ||
{ | ||
date: 1533722155401, // {number} timestamp at which the call was made | ||
args: [], // {Array} Eventual arguments the constructor has been called | ||
// with | ||
// If the call succeeded: | ||
response: mediaSource, // {MediaSource|undefined} MediaSource created | ||
responseDate: 1533722155401, // {number|undefined} timestamp at which | ||
// the response was received | ||
// If the call has trown | ||
error: someError, // {Error|undefined} If an error was thrown while | ||
// creating a new MediaSource. | ||
// If this property is set, we won't have `response` | ||
// nor `responseDate` set | ||
errorDate: 1533722155401 // {number|undefined} timestamp at which | ||
// the error was received | ||
} | ||
], | ||
methods: { | ||
addSourceBuffer: [ // Name of the method concerned | ||
{ | ||
self: mediaSource, // {Object} The value of `this` at the time of the | ||
// call (usually the MediaSource) | ||
date: 1533722155401, // {number} timestamp at which the call was made | ||
args: [], // {Array} Eventual arguments this method has been called | ||
// with | ||
// If the call succeeded: | ||
response: sourceBuffer, // {*} What has been returned by the call | ||
responseDate: 1533722155401, // {number|undefined} timestamp at which | ||
// the response was received | ||
// If the call has trown | ||
error: someError, // {Error|undefined} If an error was thrown while | ||
// calling the method. | ||
// If this property is set, we won't have `response` | ||
// nor `responseDate` set | ||
errorDate: 1533722155401 // {number|undefined} timestamp at which | ||
// the error was received | ||
} | ||
] | ||
}, | ||
properties: { | ||
duration: { // name of the property | ||
get: [ // A new entry is added each time the property is accessed | ||
{ | ||
self: mediaSource, // {MediaSource} The instance of the concerned | ||
// mediaSource | ||
date: 1533722155401, // {number} timestamp at which the property | ||
// was accessed | ||
value: 10 // {*} Content of the property as it was accessed | ||
} | ||
], | ||
set: [ // A new entry is added each time the property is set | ||
{ | ||
self: mediaSource, // {MediaSource} The instance of the concerned | ||
// mediaSource | ||
date: 1533722155401, // {number} timestamp at which the property | ||
// was set | ||
value: 15 // {*} Content the property was set to | ||
} | ||
] | ||
} | ||
} | ||
}, | ||
SourceBuffer: { // SourceBuffer follows the same structure | ||
new: [], // Note: SourceBuffer are usually created through | ||
// MediaSource.prototype.addSourceBuffer and not threw the `new` | ||
// keyword. As such, this array might always stay empty. | ||
methods: {}, | ||
properties: {}, | ||
}, | ||
} | ||
``` | ||
### Custom Logger ############################################################## | ||
If you don't like the default logging strategy or find it too verbose, a custom | ||
Logger can be defined. | ||
It is accessible through the ``MSESpy.Logger`` object. All it contains are | ||
several functions automatically called at various key points: | ||
```js | ||
Logger = { | ||
/** | ||
* Triggered each time a property is accessed. | ||
* @param {string} pathString - human-readable path to the property. | ||
* @param {*} value - the value it currently has. | ||
*/ | ||
onPropertyAccess(pathString, value) {}, | ||
/** | ||
* Triggered each time a property is set. | ||
* @param {string} pathString - human-readable path to the property. | ||
* @param {*} value - the value it is set to. | ||
*/ | ||
onSettingProperty(pathString, value) {}, | ||
/** | ||
* Triggered when some object is instanciated (just before). | ||
* @param {string} objectName - human-readable name for the concerned object. | ||
* @param {Array.<*>} args - Arguments given to the constructor | ||
*/ | ||
onObjectInstanciation(objectName, args) {}, | ||
/** | ||
* Triggered when an Object instanciation failed. | ||
* @param {string} objectName - human-readable name for the concerned object. | ||
* @param {Error} error - Error thrown by the constructor | ||
*/ | ||
onObjectInstanciationError(objectName, error) {}, | ||
/** | ||
* Triggered when an Object instanciation succeeded. | ||
* @param {string} objectName - human-readable name for the concerned object. | ||
* @param {*} value - The corresponding object instanciated. | ||
*/ | ||
onObjectInstanciationSuccess(objectName, value) {}, | ||
/** | ||
* Triggered when some method/function is called. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {Array.<*>} args - Arguments given to this function. | ||
*/ | ||
onFunctionCall(pathName, args) {}, | ||
/** | ||
* Triggered when a function call fails. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {Error} error - Error thrown by the call | ||
*/ | ||
onFunctionCallError(pathName, error) {}, | ||
/** | ||
* Triggered when a function call succeeded. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {*} value - The result of the function | ||
*/ | ||
onFunctionCallSuccess(pathName, value) {}, | ||
}; | ||
``` | ||
Note: if the code above were to be implemented, you wouldn't have any logs | ||
displaying in the console, as all functions declared here are empty. | ||
You can look at ``src/utils/logger.js`` for default implementations. |
@@ -10,3 +10,2 @@ /** | ||
properties: {}, | ||
events: {}, // TODO | ||
}, | ||
@@ -17,3 +16,2 @@ SourceBuffer: { | ||
properties: {}, | ||
events: {}, // TODO | ||
}, | ||
@@ -20,0 +18,0 @@ }; |
@@ -16,3 +16,3 @@ import { | ||
*/ | ||
function activateMSESpy() { | ||
function start() { | ||
spyOnMediaSource(); | ||
@@ -22,3 +22,3 @@ spyOnSourceBuffer(); | ||
function deactivateMSESpy() { | ||
function stop() { | ||
stopSpyingOnMediaSource(); | ||
@@ -32,4 +32,4 @@ stopSpyingOnSourceBuffer(); | ||
Logger, | ||
activateMSESpy, | ||
deactivateMSESpy, | ||
start, | ||
stop, | ||
}; |
@@ -10,35 +10,84 @@ import Logger from "./utils/logger.js"; | ||
const MEDIASOURCE_SPY_OBJECT = { | ||
readOnlyProperties: [ | ||
"sourceBuffers", | ||
"activeSourceBuffers", | ||
"readyState", | ||
], | ||
properties: [ | ||
"duration", | ||
"onsourceopen", | ||
"onsourceended", | ||
"onsourceclose", | ||
], | ||
staticMethods: [ | ||
"isTypeSupported", | ||
], | ||
methods: [ | ||
"addEventListener", | ||
"removeEventListener", | ||
"dispatchEvent", | ||
"addSourceBuffer", | ||
"removeSourceBuffer", | ||
"endOfStream", | ||
"setLiveSeekableRange", | ||
"clearLiveSeekableRange", | ||
], | ||
}; | ||
const NativeMediaSourceProtoDescriptors = | ||
Object.getOwnPropertyDescriptors(NativeMediaSource.prototype); | ||
const NativeMediaSourceIsTypeSupported = NativeMediaSource.isTypeSupported; | ||
const NativeMediaSourceStaticMethods = MEDIASOURCE_SPY_OBJECT.staticMethods | ||
.reduce((acc, methodName) => { | ||
acc[methodName] = NativeMediaSource[methodName]; | ||
return acc; | ||
}, {}); | ||
const NativeMediaSourceMethods = MEDIASOURCE_SPY_OBJECT.methods | ||
.reduce((acc, methodName) => { | ||
acc[methodName] = NativeMediaSource.prototype[methodName]; | ||
return acc; | ||
}, {}); | ||
function StubbedMediaSource(...args) { | ||
if (args.length) { | ||
Logger.debug(">>> Creating MediaSource with arguments:", args); | ||
} else { | ||
Logger.debug(">>> Creating MediaSource"); | ||
Logger.onObjectInstanciation("MediaSource", args); | ||
const now = Date.now(); | ||
const spyObj = { | ||
date: now, | ||
args, | ||
}; | ||
MSE_CALLS.MediaSource.new.push(spyObj); | ||
let nativeMediaSource; | ||
try { | ||
nativeMediaSource = new NativeMediaSource(...args); | ||
} catch (e) { | ||
Logger.onObjectInstanciationError("MediaSource", e); | ||
spyObj.error = e; | ||
spyObj.errorDate = Date.now(); | ||
throw e; | ||
} | ||
const nativeMediaSource = new NativeMediaSource(...args); | ||
Logger.debug(">>> MediaSource created:", nativeMediaSource); | ||
Logger.onObjectInstanciationSuccess("MediaSource", nativeMediaSource); | ||
spyObj.response = nativeMediaSource; | ||
spyObj.responseDate = Date.now(); | ||
return nativeMediaSource; | ||
} | ||
export default function spyOnMediaSource() { | ||
stubReadOnlyProperties( | ||
nativeMediaSource, | ||
NativeMediaSource.prototype, | ||
NativeMediaSourceProtoDescriptors, | ||
[ | ||
"sourceBuffers", | ||
"activeSourceBuffers", | ||
"readyState", | ||
], | ||
MEDIASOURCE_SPY_OBJECT.readOnlyProperties, | ||
"MediaSource.prototype", | ||
MSE_CALLS.MediaSource.properties, | ||
); | ||
stubRegularMethods( | ||
NativeMediaSource, | ||
MEDIASOURCE_SPY_OBJECT.staticMethods, | ||
"MediaSource", | ||
MSE_CALLS.MediaSource.methods, | ||
); | ||
stubProperties( | ||
nativeMediaSource, | ||
NativeMediaSource.prototype, | ||
NativeMediaSourceProtoDescriptors, | ||
[ | ||
"duration", | ||
"onsourceopen", | ||
"onsourceended", | ||
"onsourceclose", | ||
], | ||
MEDIASOURCE_SPY_OBJECT.properties, | ||
"MediaSource.prototype", | ||
@@ -48,27 +97,7 @@ MSE_CALLS.MediaSource.properties, | ||
stubRegularMethods( | ||
nativeMediaSource, | ||
[ | ||
"addEventListener", | ||
"removeEventListener", | ||
"dispatchEvent", | ||
"addSourceBuffer", | ||
"removeSourceBuffer", | ||
"endOfStream", | ||
"setLiveSeekableRange", | ||
"clearLiveSeekableRange", | ||
], | ||
NativeMediaSource.prototype, | ||
MEDIASOURCE_SPY_OBJECT.methods, | ||
"MediaSource.prototype", | ||
MSE_CALLS.MediaSource.methods, | ||
); | ||
return nativeMediaSource; | ||
} | ||
export default function spyOnMediaSource() { | ||
stubRegularMethods( | ||
NativeMediaSource, | ||
["isTypeSupported"], | ||
"MediaSource.isTypeSupported", | ||
MSE_CALLS.MediaSource.methods, | ||
); | ||
window.MediaSource = StubbedMediaSource; | ||
@@ -78,4 +107,16 @@ } | ||
export function stopSpyingOnMediaSource() { | ||
Object.defineProperties(NativeMediaSource.prototype, | ||
MEDIASOURCE_SPY_OBJECT.properties | ||
.concat(MEDIASOURCE_SPY_OBJECT.readOnlyProperties) | ||
.reduce((acc, propertyName) => { | ||
acc[propertyName] = NativeMediaSourceProtoDescriptors[propertyName]; | ||
}, {}) | ||
); | ||
MEDIASOURCE_SPY_OBJECT.staticMethods.forEach((methodName) => { | ||
NativeMediaSource[methodName] = NativeMediaSourceStaticMethods[methodName]; | ||
}); | ||
MEDIASOURCE_SPY_OBJECT.methods.forEach((methodName) => { | ||
NativeMediaSource.prototype[methodName] = NativeMediaSourceMethods[methodName]; | ||
}); | ||
window.MediaSource = NativeMediaSource; | ||
window.MediaSource.isTypeSupported = NativeMediaSourceIsTypeSupported; | ||
} |
@@ -0,1 +1,2 @@ | ||
import Logger from "./utils/logger.js"; | ||
import stubRegularMethods from "./utils/stubRegularMethods.js"; | ||
@@ -9,18 +10,69 @@ import stubReadOnlyProperties from "./utils/stubReadOnlyProperties.js"; | ||
const SOURCEBUFFER_SPY_OBJECT = { | ||
readOnlyProperties: [ | ||
"updating", | ||
"buffered", | ||
// "audioTracks", | ||
// "videoTracks", | ||
// "textTracks", | ||
], | ||
properties: [ | ||
"mode", | ||
"timestampOffset", | ||
"appendWindowStart", | ||
"appendWindowEnd", | ||
"onupdate", | ||
"onupdatestart", | ||
"onupdateend", | ||
"onerror", | ||
"onabort", | ||
], | ||
staticMethods: [], | ||
methods: [ | ||
"addEventListener", | ||
"removeEventListener", | ||
"dispatchEvent", | ||
"appendBuffer", | ||
"abort", | ||
"remove", | ||
], | ||
}; | ||
const NativeSourceBufferProtoDescriptors = | ||
Object.getOwnPropertyDescriptors(NativeSourceBuffer.prototype); | ||
const NativeSourceBufferAddEventListener = | ||
NativeSourceBuffer.prototype.addEventListener; | ||
const NativeSourceBufferRemoveEventListener = | ||
NativeSourceBuffer.prototype.removeEventListener; | ||
const NativeSourceBufferDispatchEvent = | ||
NativeSourceBuffer.prototype.dispatchEvent; | ||
const NativeSourceBufferAppendBuffer = | ||
NativeSourceBuffer.prototype.appendBuffer; | ||
const NativeSourceBufferAbort = | ||
NativeSourceBuffer.prototype.abort; | ||
const NativeSourceBufferRemove = | ||
NativeSourceBuffer.prototype.remove; | ||
const NativeSourceBufferStaticMethods = SOURCEBUFFER_SPY_OBJECT.staticMethods | ||
.reduce((acc, methodName) => { | ||
acc[methodName] = NativeSourceBuffer[methodName]; | ||
return acc; | ||
}, {}); | ||
const NativeSourceBufferMethods = SOURCEBUFFER_SPY_OBJECT.methods | ||
.reduce((acc, methodName) => { | ||
acc[methodName] = NativeSourceBuffer.prototype[methodName]; | ||
return acc; | ||
}, {}); | ||
function StubbedSourceBuffer(...args) { | ||
Logger.onObjectInstanciation("SourceBuffer", args); | ||
const now = Date.now(); | ||
const spyObj = { | ||
date: now, | ||
args, | ||
}; | ||
MSE_CALLS.SourceBuffer.new.push(spyObj); | ||
let nativeSourceBuffer; | ||
try { | ||
nativeSourceBuffer = new NativeSourceBuffer(...args); | ||
} catch (e) { | ||
Logger.onObjectInstanciationError("SourceBuffer", e); | ||
spyObj.error = e; | ||
spyObj.errorDate = Date.now(); | ||
throw e; | ||
} | ||
Logger.onObjectInstanciationSuccess("SourceBuffer", nativeSourceBuffer); | ||
spyObj.response = nativeSourceBuffer; | ||
spyObj.responseDate = Date.now(); | ||
return nativeSourceBuffer; | ||
} | ||
export default function spyOnSourceBuffer() { | ||
@@ -30,9 +82,3 @@ stubReadOnlyProperties( | ||
NativeSourceBufferProtoDescriptors, | ||
[ | ||
"updating", | ||
"buffered", | ||
// "audioTracks", | ||
// "videoTracks", | ||
// "textTracks", | ||
], | ||
SOURCEBUFFER_SPY_OBJECT.readOnlyProperties, | ||
"SourceBuffer.prototype", | ||
@@ -44,13 +90,3 @@ MSE_CALLS.SourceBuffer.properties, | ||
NativeSourceBufferProtoDescriptors, | ||
[ | ||
"mode", | ||
"timestampOffset", | ||
"appendWindowStart", | ||
"appendWindowEnd", | ||
"onupdate", | ||
"onupdatestart", | ||
"onupdateend", | ||
"onerror", | ||
"onabort", | ||
], | ||
SOURCEBUFFER_SPY_OBJECT.properties, | ||
"SourceBuffer.prototype", | ||
@@ -61,41 +97,24 @@ MSE_CALLS.SourceBuffer.properties, | ||
NativeSourceBuffer.prototype, | ||
[ | ||
"addEventListener", | ||
"removeEventListener", | ||
"dispatchEvent", | ||
"appendBuffer", | ||
"abort", | ||
"remove", | ||
], | ||
SOURCEBUFFER_SPY_OBJECT.methods, | ||
"SourceBuffer.prototype", | ||
MSE_CALLS.SourceBuffer.methods, | ||
); | ||
window.SourceBuffer = StubbedSourceBuffer; | ||
} | ||
export function stopSpyingOnSourceBuffer() { | ||
Object.defineProperties(NativeSourceBuffer.prototype, { | ||
updating: NativeSourceBufferProtoDescriptors.updating, | ||
buffered: NativeSourceBufferProtoDescriptors.buffered, | ||
mode: NativeSourceBufferProtoDescriptors.mode, | ||
timestampOffset: NativeSourceBufferProtoDescriptors.timestampOffset, | ||
appendWindowStart: NativeSourceBufferProtoDescriptors.appendWindowStart, | ||
appendWindowEnd: NativeSourceBufferProtoDescriptors.appendWindowEnd, | ||
onupdate: NativeSourceBufferProtoDescriptors.onupdate, | ||
onupdatestart: NativeSourceBufferProtoDescriptors.onupdatestart, | ||
onupdateend: NativeSourceBufferProtoDescriptors.onupdateend, | ||
onerror: NativeSourceBufferProtoDescriptors.onerror, | ||
onabort: NativeSourceBufferProtoDescriptors.onabort, | ||
Object.defineProperties(NativeSourceBuffer.prototype, | ||
SOURCEBUFFER_SPY_OBJECT.properties | ||
.concat(SOURCEBUFFER_SPY_OBJECT.readOnlyProperties) | ||
.reduce((acc, propertyName) => { | ||
acc[propertyName] = NativeSourceBufferProtoDescriptors[propertyName]; | ||
}, {}) | ||
); | ||
SOURCEBUFFER_SPY_OBJECT.staticMethods.forEach((methodName) => { | ||
NativeSourceBuffer[methodName] = NativeSourceBufferStaticMethods[methodName]; | ||
}); | ||
NativeSourceBuffer.prototype.addEventListener = | ||
NativeSourceBufferAddEventListener; | ||
NativeSourceBuffer.prototype.removeEventListener = | ||
NativeSourceBufferRemoveEventListener; | ||
NativeSourceBuffer.prototype.dispatchEvent = | ||
NativeSourceBufferDispatchEvent; | ||
NativeSourceBuffer.prototype.appendBuffer = | ||
NativeSourceBufferAppendBuffer; | ||
NativeSourceBuffer.prototype.abort = | ||
NativeSourceBufferAbort; | ||
NativeSourceBuffer.prototype.remove = | ||
NativeSourceBufferRemove; | ||
SOURCEBUFFER_SPY_OBJECT.methods.forEach((methodName) => { | ||
NativeSourceBuffer.prototype[methodName] = NativeSourceBufferMethods[methodName]; | ||
}); | ||
window.SourceBuffer = NativeSourceBuffer; | ||
} |
/** | ||
* Define the logger for startMSESpy. | ||
* Define the logger for the MSE-spy. | ||
* Allows to re-define a specific logger on runtime / before applying this | ||
@@ -7,19 +7,84 @@ * script. | ||
*/ | ||
const Logger = window.Logger || { | ||
const Logger = window.MSESpyLogger || { | ||
/* eslint-disable no-console */ | ||
log: function(...args) { | ||
console.log(...args); | ||
/** | ||
* Triggered each time a property is accessed. | ||
* @param {string} pathString - human-readable path to the property. | ||
* @param {*} value - the value it currently has. | ||
*/ | ||
onPropertyAccess(pathString, value) { | ||
console.debug(">>> Getting ${pathString}:", value); | ||
}, | ||
debug: function(...args) { | ||
console.debug(...args); | ||
/** | ||
* Triggered each time a property is set. | ||
* @param {string} pathString - human-readable path to the property. | ||
* @param {*} value - the value it is set to. | ||
*/ | ||
onSettingProperty(pathString, value) { | ||
console.debug(`>> Setting ${pathString}:`, value); | ||
}, | ||
info: function(...args) { | ||
console.info(...args); | ||
/** | ||
* Triggered when some object is instanciated (just before). | ||
* @param {string} objectName - human-readable name for the concerned object. | ||
* @param {Array.<*>} args - Arguments given to the constructor | ||
*/ | ||
onObjectInstanciation(objectName, args) { | ||
if (args.length) { | ||
console.debug(">>> Creating ${objectName} with arguments:", args); | ||
} else { | ||
console.debug(">>> Creating ${objectName}"); | ||
} | ||
}, | ||
error: function(...args) { | ||
console.error(...args); | ||
/** | ||
* Triggered when an Object instanciation failed. | ||
* @param {string} objectName - human-readable name for the concerned object. | ||
* @param {Error} error - Error thrown by the constructor | ||
*/ | ||
onObjectInstanciationError(objectName, error) { | ||
console.error(">> ${objectName} creation failed:", error); | ||
}, | ||
warning: function(...args) { | ||
console.warning(...args); | ||
/** | ||
* Triggered when an Object instanciation succeeded. | ||
* @param {string} objectName - human-readable name for the concerned object. | ||
* @param {*} value - The corresponding object instanciated. | ||
*/ | ||
onObjectInstanciationSuccess(objectName, value) { | ||
console.debug(">>> ${objectName} created:", value); | ||
}, | ||
/** | ||
* Triggered when some method/function is called. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {Array.<*>} args - Arguments given to this function. | ||
*/ | ||
onFunctionCall(pathName, args) { | ||
if (args.length) { | ||
console.debug(`>>> ${pathName} called with arguments:`, args); | ||
} else { | ||
console.debug(`>>> ${pathName} called`); | ||
} | ||
}, | ||
/** | ||
* Triggered when a function call fails. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {Error} error - Error thrown by the call | ||
*/ | ||
onFunctionCallError(pathName, error) { | ||
console.error(`>> ${pathName} failed:`, error); | ||
}, | ||
/** | ||
* Triggered when a function call succeeded. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {*} value - The result of the function | ||
*/ | ||
onFunctionCallSuccess(pathName, value) { | ||
console.info(">>> ${pathName} succeeded:", value); | ||
}, | ||
/* eslint-enable no-console */ | ||
@@ -26,0 +91,0 @@ }; |
@@ -22,4 +22,4 @@ import Logger from "./logger.js"; | ||
get() { | ||
Logger.onPropertyAccess(completePath, value); | ||
const value = oldDescriptor.get.bind(this)(); | ||
const myObj = { | ||
@@ -39,8 +39,6 @@ self: this, | ||
Logger.debug(`>> Getting ${completePath}:`, value); | ||
return value; | ||
}, | ||
set(value) { | ||
Logger.debug(`>> Setting ${completePath}:`, value); | ||
Logger.onSettingProperty(completePath, value); | ||
const myObj = { | ||
@@ -47,0 +45,0 @@ self: this, |
@@ -22,4 +22,4 @@ import Logger from "./logger.js"; | ||
get() { | ||
Logger.onPropertyAccess(completePath, value); | ||
const value = oldDescriptor.get.bind(this)(); | ||
const myObj = { | ||
@@ -30,3 +30,2 @@ self: this, | ||
}; | ||
if (!logObj[propertyName]) { | ||
@@ -38,4 +37,2 @@ logObj[propertyName] = { | ||
logObj[propertyName].get.push(myObj); | ||
Logger.debug(`>> Getting ${completePath}:`, value); | ||
return value; | ||
@@ -42,0 +39,0 @@ }, |
import Logger from "./logger.js"; | ||
import onAPICall from "./onAPICall.js"; | ||
@@ -20,3 +19,3 @@ export default function stubRegularMethods( | ||
obj[methodName] = function (...args) { | ||
onAPICall(completePath, args); | ||
Logger.onFunctionCall(completePath, args); | ||
const myObj = { | ||
@@ -37,3 +36,3 @@ self: obj, | ||
} catch (e) { | ||
Logger.error(`>> ${completePath} failed:`, e); | ||
Logger.onFunctionCallError(completePath, e); | ||
myObj.error = e; | ||
@@ -43,3 +42,3 @@ myObj.errorDate = Date.now(); | ||
} | ||
Logger.debug(`>> ${completePath} succeeded:`, res); | ||
Logger.onFunctionCallSuccess(completePath, res); | ||
myObj.response = res; | ||
@@ -46,0 +45,0 @@ myObj.responseDate = Date.now(); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
45384
890
311
15