Problems in playing Fairplay Encrypted content on Safari browser - javascript

We are trying to write player in Javascript which should play fairplay encrypted content on safari browser. We figured 'encrypted' event is not supported on Safari browser and we added event listener for 'WebKitNeedKey' event. We coded the flow as below.
addEventListener('webkitneedkey, onWebKitNeedKey);
onWebKitNeedKey(evt) {
videoElement = document.getElementById('videoID');
if (videoElement.webkitKeys) {
videoElement.webkitSetMediaKeys(new WebKitMediaKeys('com.apple.fps.2_0'));
}
const session = videoElement.webkitKeys.createSession('video/mp4', event.initData);
session.addEventListener('webkitkeymessage', onWebKitKeyMessage);
session.addEventListener('webkitkeyerror', onWebKitKeyError);
session.addEventListener('webkitkeyadded', onWebKitKeyAdded);
}
onWebKitKeyMessage(evt) {
console.log(`received webkit key message : ${evt}`);
}
onWebKitKeyError(evt) {
console.log(`received webkit key error : ${evt}`);
}
onWebKitKeyAdded(evt) {
console.log(`received webkit key added : ${evt}`);
}
Now I am getting the webkitneedkey event and after setting the keys, I am getting webkitkeymessage event. I am planning to implement the logic of contacting the server for the license as per https://github.com/WebPlatformForEmbedded/WPEWebKit/blob/master/LayoutTests/http/tests/media/clearkey/clear-key-hls-aes128.html
I have following questions. Can any one please help me to resolve the below questions?
1) Do we need to set the source to '.m3u8'? Is it mandatory? I am getting the events even without setting the source to .m3u8
2) Is my approach correct in onWebKitNeedKey? Can I send evt.initData directly to webkitSetMediaKeys without modifying? Do I need to extract the content ID from evt.initData if I use key as 'com.apple.fps.2_0' instead of 'com.apple.fps.1_0'?

Related

Chrome, allow autoconnect for HID device

So I'm trying to read out a USB-scale thats connected to my pc. I use chrome's experimental HID api.
I use Tampermonekey as userscript injector to extend a website's functionality.
The script I inject looks like this:
navigator.hid.requestDevice({ filters: [{ vendorId: 0x0922, productId: 0x8003}] }).then((devices) => {
if (devices.length == 0) return;
devices[0].open().then(() => {
if(disconnected) {
disconnected = false
}
console.log("Opened device: " + devices[0].productName);
devices[0].addEventListener("inputreport", handleInputReport);
devices[0].sendReport(outputReportId, outputReport).then(() => {
console.log("Sent output report " + outputReportId);
});
});
});
When I run it just like this(inline) I get the message in chrome:
DOMException: Failed to execute 'requestDevice' on 'HID': Must be handling a user gesture to show a permission request.
Basically, the code needs to be inside an event listener and the listener needs to be triggered by user input to run.
Al fine and dandy, except that this has to be initialized hundreds of times a day. I tried running this code in edge and here it just works without user input.
Is there a way I can disable this security feature(completely or only for the site im using it on) in chrome? I know edge is based on chromium so I expect it to be possible, but am unable to find how/where
You can use HID.getDevices() to retrieve an HID device that the user has already granted access to.
My suggestion would be to check for the device you want with getDevices first. If you can't find the device, then make something the user can interact with that will allow you to use requestDevice to connect to the device.

How do you obtain permissions from web-nfc API?

I'm trying to get Web NFC to work through the Web NFC API, but I can't get it past an error message of NotAllowedError: NFC permission request denied.
I'm using this on Chrome 89 Dev on a Windows 10 computer, and the source code is being run locally.
I have tried the examples posted on the Internet also, including the Google sample but it returns the same error. I'm not concerned with it being experimental at this point as referring to this does show it has successfully passed the necessary tests, including permissions.
The HTML/JS code I'm using is below, and I've read the specification point 9.3, but I can't make sense of it to write it as code, so is there a guideline algorithm that would be helpful here to resolve this?
async function readTag() {
if ("NDEFReader" in window) {
const reader = new NDEFReader();
try {
await reader.scan();
reader.onreading = event => {
const decoder = new TextDecoder();
for (const record of event.message.records) {
consoleLog("Record type: " + record.recordType);
consoleLog("MIME type: " + record.mediaType);
consoleLog("=== data ===\n" + decoder.decode(record.data));
}
}
} catch(error) {
consoleLog(error);
}
} else {
consoleLog("Web NFC is not supported.");
}
}
async function writeTag() {
if ("NDEFWriter" in window) {
const writer = new NDEFWriter();
try {
await writer.write("helloworld");
consoleLog("NDEF message written!");
} catch(error) {
consoleLog(error);
}
} else {
consoleLog("Web NFC is not supported.");
}
}
function consoleLog(data) {
var logElement = document.getElementById('log');
logElement.innerHTML += data + '\n';
};
<!DOCTYPE html>
<html>
<head>
<script src="webnfc.js"></script>
</head>
<body>
<p>
<button onclick="readTag()">Test NFC Read</button>
<button onclick="writeTag()">Test NFC Write</button>
</p>
<pre id="log"></pre>
</body>
</html>
From https://web.dev/nfc/#security-and-permissions
Web NFC is only available to top-level frames and secure browsing contexts (HTTPS only). Origins must first request the "nfc" permission while handling a user gesture (e.g a button click). The NDEFReader scan() and write() methods trigger a user prompt, if access was not previously granted.
I guess you are running from a file:// URL as you said "locally" which is not supported.
You need to host it from a local web server using a https:// URL
Once in the right scope trying to scan or write should trigger a user prompt.
You can also check permissions see https://web.dev/nfc/#check-for-permission
Update:
So I tried the sample page https://googlechrome.github.io/samples/web-nfc/
And this works for me on Android Chrome 87 with "Experimental Web Platform features" enabled
When you hit the scan button A dialog asking for permission pops up.
Comparing the code in this sample to yours I notice that does:-
ndef.addEventListener("reading" , ({ message, serialNumber }) => { ...
Where as yours does:-
ndef.onreading = event => { ...
I don't know if it is the style setting what happens on the Event or something else (Hey this is all experimental)
Update2
To answer the question from the comments of Desktop support.
So you should be some of the desktop/browser combinations at the moment and may be in the future there will be wider support as this is no longer experimental standards. Obviously as your test link suggest Chrome on a Linux Desktop should work as this is really similar to Android Support, with all the NFC device handling done by libnfc and the browser just has to know about this library instead of every type usb or other device than can do NFC.
From what seen of NFC support on Windows, most of this is focussed on direct controlling the NFC reader via USB as just another USB device, while there is a libnfc equivalent in Windows.Networking.Proximity API's I've not come across any NFC reader saying they support this or anybody using it.
For Mac Deskstop, given that Apple are behind the curve with NFC support in iOS, I feel their desktop support will be even further behind even though it could be similar to Linux.
As you can read at https://web.dev/nfc/#browser-support, Web NFC only supports Android for now which is why you get "NotAllowedError: NFC permission request denied." error on Windows.

Detect removal of restriction of devices

Using getUserMedia to let user select microphone. Further I use enumerateDevices to create a select with devices so that user can change device from UI.
I am on Firefox and have not checked how others browsers fare, but at least for FF I have not found a solution.
If user selects to not allow access when asked one do not get to ask again[ 1 ] until user removes the restriction:
Question is if there is a way to detect when user removes the restriction?
Scenario is typically:
User loads page and are asked to select input device
User rejects
UI disables device selector + hide various stuff
User removes restriction (as per picture above)
UI enables device selector + unhide various stuff
There is (obviously) not a way to reset the block from client side by Java Script, but is there a way to detect that user revokes the block? (Or is there? Sounds like something that could be exploited to keep looping a request for access.)
One could do a loop where one keep trying for the lifetime of the page, but would like to avoid that. Looking for an event for this.
In that regard, ondevicechange, does not trigger an event when block is removed - which is logical in a way as there is not a change in the available devices, in a way :P.
[ 1 ] That is: one can ask, but it result in a:
MediaStreamError
message: The request is not allowed by the user agent or the platform in the current context.
name: NotAllowedError
​
You should be able to detect this change from the Permissions API.
The PermissionStatus object returned by Permissions.prototype.query() has an onchange event handler. So a query to the "camera" or "microphone" will fire its change event when the user changes their setting.
const camera_perm = await navigator.permissions.query( { name: 'camera' } );
camera_perm.onchange = (evt) => {
const allowed = camera_perm.state === "granted";
if( allowed ) {
// ...
}
else {
}
};
This currently works in Chrome, but Firefox still doesn't support neither the "camera" nor the "microphone" members of PermissionDescriptor.
So for that browser, the closest we can have is through polling, as explained in this Q/A: Event listener that "camera and microphone blocked" is allowed .

Speech gets cut off in firefox when page is auto-refreshed but not in google chrome

I have this problem where in firefox the speech gets cut off if the page is auto-refreshed, but in google chrome it finishes saying the speech even if the page is auto-refreshed. How do I fix it so that the speech doesn't get cut off in firefox even when the page is auto-refreshed?
msg = new SpeechSynthesisUtterance("please finish saying this entire sentence.");
window.speechSynthesis.speak(msg);
(function ($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var interval;
interval = 750;
setTimeout(function () {
myframe.attr({ src: location.href });
}, interval);
});
}
})(jQuery);
I have this problem where in firefox the speech gets cut off if the
page is auto-refreshed, but in google chrome it finishes saying the
speech even if the page is auto-refreshed.
The described behaviour for Firefox is a sane expected implementation.
Browsing the source code of Firefox and Chromium the implementation of speechSynthesis.speak() is based on a socket connection with the local speech server. That server at *nix is usually speech-dispatcher or speechd (speech-dispatcher). See How to programmatically send a unix socket command to a system server autospawned by browser or convert JavaScript to C++ souce code for Chromium? for description of trying to implement SSML parsing at Chromium.
Eventually decided to write own code to achieve that requirement using JavaScript according to the W3C specification SpeechSynthesisSSMLParser after asking more than one question at SE sites, filing issues and bugs and posting on W3C mailings lists without any evidence that SSML parsing would ever be included as part of the Web Speech API.
Once that connection is initiated a queue is created for calls to .speak(). Even when the connection is closed Task Manager might still show the active process registered by the service.
The process at Chromium/Chrome is not without bugs, the closest that have filed to what is being described at the question is
Issue 797624: "speak speak slash" is audio output of .speak() following two calls to .speak(), .pause() and .resume()
Why hasn't Issue 88072 and Issue 795371 been answered? Are Internals>SpeechSynthesis and Blink>Speech dead? (for possible reason why "but in google chrome it finishes saying the speech even if the page is auto-refreshed." is still possible at Chrome)
.volume property issues
Issue 797512: Setting SpeechSynthesisUtterance.volume does not change volume of audio output of speechSynthesis.speak() (Chromium/Chrome)
Bug 1426978 Setting SpeechSynthesisUtterance.volume does not change volume of audio output of speechSynthesis.speak() (Firefox)
The most egregious issue being Chromium/Chrome webkitSpeechReconition implementation which records the users' audio and posts that audio data to a remote service, where a transcript is returned to the browser - without explicitly notifying the user that is taking place, marked WONT FIX
Issue 816095: Does webkitSpeechRecognition send recorded audio to a remote web service by default?
Relevant W3C Speech API issues at GitHub
The UA should be able to disallow speak() from autoplaying #27
Precisely define when speak() should fail due to autoplay rules #35 (ironically, relevant to the reported behaviour at Chromium/Chrome and output described at this question, see Web Audio, Autoplay Policy and Games and Autoplay Policy Changes)
Intent to Deprecate: speechSynthesis.speak without user activation
Summary
The SpeechSynthesis API is actively being abused on the web. We don’t have hard data on abuse, but since other autoplay avenues are
starting to be closed, abuse is anecdotally moving to the Web Speech
API, which doesn't follow autoplay rules.
After deprecation, the plan is to cause speechSynthesis.speak to
immediately fire an error if specific autoplay rules are not
satisfied. This will align it with other audio APIs in Chrome.
Timing of SpeechSynthesis state changes not defined #39
Timing of SpeechSynthesisUtterance events firing not defined #40
Clarify what happens if two windows try to speak #47
In summary, would not describe the behaviour at Firefox as a "problem", but the behaviour at Chrome as being a potential "problem".
Diving in to W3C Web Speech API implementation at browsers is not a trivial task. For several reasons. Including the apparent focus, or available option of, commercial TTS/SST services and proprietary, closed-source implementations of speech synthesis and speech recognition in "smart phones"; in lieu of fixing the various issues with the actual deployment of the W3C Web Speech API at modern browsers.
The maintainers of speechd (speech-dispatcher) are very helpful with regards to the server side (local speech-dispatcher socket).
Cannot speak for Firefox maintainers. Would estimate it is unlikely that if a bug is filed relevant to the feature request of continuing execution of audio output by .speak() from reloaded window is consistent with recent autoplay policies implemented by browsers. Though you can still file a Firefox bug to ask if audio output (from any API or interface) is expected to continue during reload of the current window; and if there are any preferences or policies which can be set to override the described behaviour, as suggested at the answer by #zip. And get the answer from the implementers themselves.
There are individuals and groups that compose FOSS code which are active in the domain and willing to help SST/TTS development, many of which are active at GitHub, which is another option to ask questions about how to implement what you are trying to achieve specifically at Firefox browser.
Outside of asking implementers for the feature request, you can read the source code and try create one or more workarounds. Alternatives include using meSpeak.js, though that still does not necessarily address if Firefox is intentionally blocking audio output during reload of the window.
Not sure why there's a difference in behavior... guest271314 might be on to something in his answer. However, you may be able to prevent FF from stopping the tts by intercepting the reload event with a onbeforeunload handler and waiting for the utterance to finish:
msg = new SpeechSynthesisUtterance("say something");
window.speechSynthesis.speak(msg);
window.onbeforeunload = function(e) {
if(window.speechSynthesis.speaking){
event.preventDefault();
msg.addEventListener('end', function(event) {
//logic to continue unload here
});
}
};
EDITED: See more elegant solution with promises below initial answer!
Below snippet is a workaround to the browser inconsistencies found in Firefox, checking synth.speaking in the interval and only triggering a reload if it's false prevents the synth from cutting of prematurely:
(It does not NOT work properly in the SO snippet, I assume it doesn't like iFrames in iFrames or whatever, just copy paste the code in a file and open it with Firefox!)
<p>I'm in the body, but will be in an iFrame</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var synth = window.speechSynthesis;
msg = new SpeechSynthesisUtterance("please finish saying this entire sentence.");
synth.speak(msg);
(function ($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var interval;
interval = setInterval(function () {
if (!synth.speaking) {
myframe.attr({ src: location.href });
clearInterval(interval);
}
}, 750);
});
}
})(jQuery);
</script>
A more elaborate solution could be to not have any setTimeout() or setInterval() at all, but use promises instead. Like this the page will simply reload whenever the message is done synthesizing, no matter how short or long it is. This will also prevent the "double"/overlapping-speech on the initial pageload. Not sure if this helps in your scenario, but here you go:
<button id="toggleSpeech">Stop Speaking!</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
if (window == window.top) {
window.speech = {
say: function(msg) {
return new Promise(function(resolve, reject) {
if (!SpeechSynthesisUtterance) {
reject('Web Speech API is not supported');
}
var utterance = new SpeechSynthesisUtterance(msg);
utterance.addEventListener('end', function() {
resolve();
});
utterance.addEventListener('error', function(event) {
reject('An error has occurred while speaking: ' + event.error);
});
window.speechSynthesis.speak(utterance);
});
},
speak: true,
};
}
(function($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var $iframe = $(this).contents();
$iframe.find('#toggleSpeech').on('click', function(e) {
console.log('speaking will stop when the last sentence is done...');
window.speech.speak = !window.speech.speak;
});
window.speech.say('please finish saying this entire sentence.')
.then(function() {
if ( window.speech.speak ) {
console.log('speaking done, reloading iframe!');
myframe.attr({ src: location.href });
}
});
});
}
})(jQuery);
</script>
NOTE: Chrome (since v70) does NOT allow the immediate calling of window.speechSynthesis.speak(new SpeechSynthesisUtterance(msg)) anymore, you will get an error speechSynthesis.speak() without user activation is no longer allowed..., more details here. So technically the user would have to activate the script in Chrome to make it work!
Firefox:
First of all type and search for the “about: config” inside the browser by filling it in the address bar. This will take to another page where there will be a pop up asking to Take Any Risk, you need to accept that. Look for the preference named “accessibility.blockautorefresh” from the list and then right-click over that. There will be some options appearing as the list on the screen, select the Toggle option and then set it to True rather than False. This change will block the Auto Refresh on the Firefox browser. Remember that this option is revertable!

How to detect Chrome extension uninstall

I am trying to detect whether my extension was uninstalled.
I can't use chrome.management.onUninstalled because it will be fired on other extension.
As of Chrome 41, you can now open a URL when the extension is uninstalled. That could contain an exit survey or track the uninstall event as some sort of analytics.
Google Chrome, unlike Firefox, doesn’t allow to detect when the user uninstalls the extension, which is quite useful to understand user behaviour.
There is a feature request on crbug.com with a discussion of this feature but it has not been implemented yet.
You can call chrome.runtime.setUninstallURL("www.example.com/survey") and redirect user to a url. Unfortunately, as soon as the extension is removed, the background script is removed too, and you can't do anything like log event or send hit to google analytics.
What I did is to set the redirect url to my server endpoint, and do some tasks like logging event to my own db, or sending hit to google analytics (ga hit builder). Then call res.status(301).redirect("www.example.com/survey") to some survey url. Finally I can send the uninstall event to google analysis.
If you're on Manifest V3, you can add it on your onInstalled Listener. If you want to capture uninstall for existing users as well, you need to add it to 'update' as well.
Place this code in your background page:
chrome.runtime.onInstalled.addListener(function (details) {
if (details.reason == 'install') {
... can add things like sending a user to a tutorial page on your website
chrome.runtime.setUninstallURL('https://www.yourwebsite.com/uninstall');
} else if (details.reason == 'update') {
... can add things like sending user to a update page on your website
chrome.runtime.setUninstallURL('https://www.yourwebsite.com/uninstall');
}
});
Find more information here: https://developer.chrome.com/docs/extensions/reference/runtime/#method-setUninstallURL
For mv3: An easy way would be to have
// Redirect users to a form when the extension is uninstalled.
const uninstallListener = (details) => {
if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
chrome.runtime.setUninstallURL('https://forms.gle/...');
}
if (details.reason === chrome.runtime.OnInstalledReason.UPDATE) {
// TODO: show changelog
}
};
chrome.runtime.onInstalled.addListener(uninstallListener);
Place it in your background.
Content Script can Detect an Uninstall
Simply check the value of chrome.runtime, which becomes undefined when an extension is uninstalled.
A good trigger to check this is port disconnect:
// content_script.js
const port = chrome.runtime.connect();
port.onDisconnect.addListener(onPortDisconnect);
function onPortDisconnect() {
// After the extension is disabled/uninstalled, `chrome.runtime` may take
// a few milliseconds to get cleared, so use a delay before checking.
setTimeout(() => {
if (!chrome.runtime?.id) {
console.log('Extension disabled!');
}
}, 1000);
};

Categories

Resources