// ==UserScript== // @name EME Logger // @namespace http://greasyfork.org/ // @version 0.2 // @description Inject EME interface and log its function calls. // @author cramer // @match *://*/* // @run-at document-start // @grant none // @downloadURL none // ==/UserScript== (async () => { const indent = (s,n=4) => s.split('\n').map(l=>Array(n).fill(' ').join('')+l).join('\n'); const b64 = { decode: s => Uint8Array.from(atob(s), c => c.charCodeAt(0)), encode: b => btoa(String.fromCharCode(...new Uint8Array(b))) }; const fnproxy = (object, func) => new Proxy(object, { apply: func }); const proxy = (object, key, func) => Object.defineProperty(object, key, { value: fnproxy(object[key], func) }); proxy(Navigator.prototype, 'requestMediaKeySystemAccess', async (_target, _this, _args) => { const [keySystem, supportedConfigurations] = _args; console.log( `[EME] Navigator::requestMediaKeySystemAccess\n` + ` Key System: ${keySystem}\n` + ` Supported Configurations:\n` + indent(JSON.stringify(supportedConfigurations, null, ' ')) ); console.trace(); return _target.apply(_this, _args); }); proxy(MediaKeySystemAccess.prototype, 'createMediaKeys', async (_target, _this, _args) => { console.log( `[EME] MediaKeySystemAccess::createMediaKeys\n` + ` Key System: ${_this.keySystem}\n` + ` Configurations:\n` + indent(JSON.stringify(_this.getConfiguration(), null, ' ')) ); console.trace(); return _target.apply(_this, _args); }); proxy(MediaKeys.prototype, 'setServerCertificate', async (_target, _this, _args) => { const [serverCertificate] = _args; console.log( `[EME] MediaKeys::setServerCertificate\n` + ` Server Certificate: ${b64.encode(serverCertificate)}` ); console.trace(); return _target.apply(_this, _args); }); proxy(MediaKeys.prototype, 'createSession', (_target, _this, _args) => { const [sessionType] = _args; console.log( `[EME] MediaKeys::createSession\n` + ` Session Type: ${sessionType || 'temporary (default)'}` ); console.trace(); return _target.apply(_this, _args); }); proxy(EventTarget.prototype, 'addEventListener', async (_target, _this, _args) => { const [type, listener] = _args; if (_this instanceof MediaKeySession) { switch(type) { case 'keystatuseschange': { _args[1] = fnproxy(listener, (_target, _this, _args) => { const [event] = _args; const keySession = event.target; const {sessionId} = keySession; console.log( `[EME] MediaKeySession::keystatuseschange\n` + ` Session ID: ${sessionId || '(not available)'}\n` + Array.from(keySession.keyStatuses).map(([keyId, status]) => ` [${status.toUpperCase()}] ${b64.encode(keyId)}` ).join('\n') ); console.trace(); return _target.apply(_this, _args);; }); break; } case 'message': { _args[1] = fnproxy(listener, (_target, _this, _args) => { const [event] = _args; const keySession = event.target; const {sessionId} = keySession; const {message} = event; console.log( `[EME] MediaKeySession::message\n` + ` Session ID: ${sessionId || '(not available)'}\n` + ` Message: ${b64.encode(message)}` ); console.trace(); return _target.apply(_this, _args);; }); break; } } } return _target.apply(_this, _args); }); proxy(MediaKeySession.prototype, 'generateRequest', async (_target, _this, _args) => { const [initDataType, initData] = _args; console.log( `[EME] MediaKeySession::generateRequest\n` + ` Session ID: ${_this.sessionId || '(not available)'}\n` + ` Init Data Type: ${initDataType}\n` + ` Init Data: ${b64.encode(initData)}` ); console.trace(); return _target.apply(_this, _args); }); proxy(MediaKeySession.prototype, 'load', async (_target, _this, _args) => { const [sessionId] = _args; console.log( `[EME] MediaKeySession::load\n` + ` Session ID: ${sessionId || '(not available)'}` ); console.trace(); return _target.apply(_this, _args); }); proxy(MediaKeySession.prototype, 'update', async (_target, _this, _args) => { const [response] = _args; console.log( `[EME] MediaKeySession::update\n` + ` Session ID: ${_this.sessionId || '(not available)'}\n` + ` Response: ${b64.encode(response)}` ); console.trace(); return _target.apply(_this, _args); }); proxy(MediaKeySession.prototype, 'close', async (_target, _this, _args) => { console.log( `[EME] MediaKeySession::close\n` + ` Session ID: ${_this.sessionId || '(not available)'}` ); console.trace(); return _target.apply(_this, _args); }); proxy(MediaKeySession.prototype, 'remove', async (_target, _this, _args) => { console.log( `[EME] MediaKeySession::remove\n` + ` Session ID: ${_this.sessionId || '(not available)'}` ); console.trace(); return _target.apply(_this, _args); }); })();