We've been seeing our realtime documents not reconnecting themselves properly for some time now after disconnecting/connecting WiFi. In particular Safari has a 100% success rate at reproducing this issue for us. Chrome also is susceptible to this same issue although it does a significantly better job than Safari at getting the Realtime API re-connected.
The issue appears to be related to how/if the bind/save XHR requests will be reported as failed to JS when the browser is not connected to the internet. In the case of Safari these requests never fully fail and instead sit and wait forever as no timeout is specified along with the XHR. Once this happens the Realtime API does not try to reconnect itself.
Below are the files that repro this issue 100% in Safari. They will prompt to wait/disable/enable WiFi. Each step may take ~10-20s. Additional information is in the comments. They are also available directly from https://drive.google.com/file/d/0B1es-bMybSeSUmRqX2JJd3dqRFE/view?usp=sharing. The ClientID and ApiKey may need to be modified to make them work properly.
Is anyone else running into similar issues or seen behavior that could be explained by this? Any reasonable workaround ideas apart from detecting this case and clobbering the document?
Edit: And of course 15min after posting this, a potential workaround popped into my head. Not pretty by any stretch of the imagination and may cause other issues, but hijacking the XHR to manually set a timeout to ensure the requests expire appears to fix the issue. [Edit3: Updated to scoped workaround with 70s timeout.]
Workaround
var timeoutURLs =
[
'https://drive.google.com/otservice/save',
'https://drive.google.com/otservice/bind'
];
function shouldSetXHRTimeout(xhr)
{
var shouldSetTimeout = false;
var URLMatch = xhr.url.split('?')[0];
if (timeoutURLs.indexOf(URLMatch) >= 0)
{
shouldSetTimeout = true;
}
return shouldSetTimeout;
}
function wrapXHR()
{
var __send = window.XMLHttpRequest.prototype.send;
window.XMLHttpRequest.prototype.send = function (data)
{
if (!this.timeout && shouldWrapXHRTimeout(this)
{
this.timeout = 70000;
}
__send.call(this, data);
};
};
wrapXHR();
gdrive_connect.html
<!DOCTYPE html>
<html>
<head>
<title>Duchess</title>
<script type="text/javascript" src="//apis.google.com/js/client.js?onload=GoogleApiLoaded" async defer></script>
<script src="gdrive_connect.js" type="application/javascript" ></script>
</head>
<body>
<div id="status">Setting up scenario...</div>
<button id="button" onclick="runScenario()">Run Scenario</button>
</body>
gdrive_connect.js
var clientId = "597847337936.apps.googleusercontent.com";
var REALTIME_MIMETYPE = 'application/vnd.google-apps.drive-sdk';
//
// Problem
// The realtime lib is not reconnecting itself and pushing changes back to the remote server
// after getting disconnected from WiFi in some cases. That is, disconnecting WiFi then reconnecting can
// result in the Realtime API no longer sending or receiving updates for the document that it believes is
// connected.
//
// More Info
// We spent a while tracking down the cause of the issue, and it appears to be with how the bind/save calls
// for the Realtime API are handled if they just 'never return'. In the reconnection failure cases, it
// appears that these network requests never completely 'fail' and instead indefinitely wait without
// notifying the XHR handler.
//
// Hardware
// This was reprod on a MacBook Pro w/ OSX 10.11 on both public WiFi as well as a home WiFi setup.
//
// Software
// This issue repros in both Chrome and Safari. In Safari it happens 100% of the time and will be a
// significantly more consistent repro. Chrome it only happens occasionally and can be fixed by opening and
// closing the laptop. In Chrome this appears to have a different effect on how hung network requests are
// handled than just disabling/enabling WiFi.
//
// Safari
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11) AppleWebKit/601.1.56 (KHTML, like Gecko) Version/9.0 Safari/601.1.56
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/601.4.4 (KHTML, like Gecko) Version/9.0.3 Safari/601.4.4
// Chrome
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36
//
// Repro
// 0) Ensure that the browser/machine are properly connected to the internet and authorized.
// 1) Authorize and load realtime doc.
// 2) Make modification to the document.
// 3) Verify modification was saved remotely.
// 4) Disable WiFi on machine.
// 5) Make modification to the document.
// 6) Verify that the modification did not get saved remotely as machine is offline.
// 7) Enable WiFi on machine.
// 8) Check document's saveDelay to verify that the remote change was not propagated.
// 9) Make additional modification to document.
// 10) Verify that none of the changes have been saved remotely.
//
// The repro requires that you manually disable/enable WiFi and should prompt when to do so. All other info
// is spit out into the console about all of the online/offline related status. When a non-network !!!ERRROR
// is displayed, this means that the repro has been successful. The browser is 100% online, the user is
// authenticated but the realtime document is not flushing its changes to the remote server.
//
// Additional Repro
// While the repro page is enough to 100% consistently cause reconnection issues in Safari, Chrome will fix
// itself in most cases. Disabling/Enabling the WiFi rapidly right after a 'bind' request is made will
// generally also get Chrome stuck not reconnecting to the server.
//
// Console Key
// Delay: The realtime doc's 'saveDelay' field.
// Modified: Time since last modification was made to the doc.
// IsOnline: Whether the browser thinks it is online (navigator.onLine).
// DriveConn: Result of making a separate authenticated request to drive through gapi.
// TokenValid: Whether the gapi.auth authroization token is still currently valid.
// IsOpen: The realtime doc's '!isClosed' field.
//
// The realtime document is accessible through 'window.remoteDoc'.
//
var ReproSteps =
[
'Setting up scenario...', // 0
'Click button to start', // 1
'Connecting...', // 2
'Running... please wait ~10s', // 3
'Disable WiFi', // 4
'Enable WiFi', // 5
'Running... please wait ~10s', // 6
'Done! See error in console' // 7
];
var currentStep = 0;
function setReproStep(index)
{
if (index >= currentStep)
{
var msg = ReproSteps[index];
document.getElementById('status').innerText = msg;
}
}
function GoogleApiLoaded()
{
console.log('Google Remote Client Lib Loaded');
gapi.auth.init();
gapi.client.setApiKey('AIzaSyB9HEdSJ-nhLJG_ssSSqhI2DX74GSiKSao');
gapi.load('auth:client,drive-realtime', function ()
{
console.log('GAPI Loaded Libs');
setReproStep(1);
});
}
function createRealtimeFile(title, description, callback)
{
console.log('Creating Drive Document');
gapi.client.drive.files.insert({
'resource':
{
'mimeType': REALTIME_MIMETYPE,
'title': title,
'description': description
}
}).execute(function (docInfo)
{
callback(docInfo, /*newDoc*/true);
});
}
function openRealtimeFile(title, callback)
{
gapi.client.load('drive', 'v2', function ()
{
gapi.client.drive.files.list(
{
'q': 'title='+"'"+title+"' and 'me' in owners and trashed=false"
}).execute(function (results)
{
if (!results.items || results.items.length === 0)
{
createRealtimeFile(title, /*DocDescription*/"", callback);
}
else
{
callback(results.items[0], /*newDoc*/false);
}
});
});
}
function runScenario()
{
console.log('Page Loaded');
document.getElementById('button').style.display = 'none';
setReproStep(2);
var GScope =
{
Drive: 'https://www.googleapis.com/auth/drive.file'
};
var handleAuthResult = function (authResult)
{
console.log('Requesting Drive Document');
openRealtimeFile("TESTDOC__", function (docInfo, newDoc)
{
if (docInfo && docInfo.id)
{
gapi.drive.realtime.load(docInfo.id, onDocLoaded, onDocInitialized, onDocLoadError);
}
else
{
console.log('Unable to find realtime doc');
debugger;
}
});
};
gapi.auth.authorize(
{
client_id: clientId,
scope: [ GScope.Drive ],
immediate: false
}, handleAuthResult);
}
function onDocInitialized(model)
{
console.log('Drive Document Initialized');
var docRoot = model.createMap();
model.getRoot().set('docRoot', docRoot);
}
var testMap;
var docDataCounter = 0;
var lastWrite = 0;
var remoteDoc;
function onDocLoaded(doc)
{
setReproStep(3);
remoteDoc = doc;
var docModel = doc.getModel();
var docRoot = docModel.getRoot();
console.log('Drive Document Loaded');
// If the loaded document has already been used to test, delete any previous data.
if (docRoot.has('testMap'))
{
console.log('Previous test detected: ' + docRoot.get('testMap').get('testData'));
docRoot.delete('testMap');
}
docRoot.set('testMap', docModel.createMap());
testMap = docRoot.get('testMap');
console.assert(testMap, 'Test map required');
makeDriveDocChange();
doc.addEventListener(gapi.drive.realtime.EventType.DOCUMENT_SAVE_STATE_CHANGED, onSaveStateChange);
beginRunningTest();
}
var VerificationTime = 5000;
var ModificationTime = 10000;
function beginRunningTest()
{
verifyConnectionState();
setTimeout(setReproStep, ModificationTime * 2, 4);
}
var verificationCount = 0;
function verifyConnectionState()
{
setTimeout(verifyConnectionState, VerificationTime);
var saveDelay = remoteDoc.saveDelay;
var isClosed = remoteDoc.isClosed;
var lastModification = Date.now() - lastWrite;
var browserOnline = navigator.onLine;
var currentCount = ++verificationCount;
if (!browserOnline && saveDelay > ModificationTime)
{
setReproStep(5);
setTimeout(setReproStep, ModificationTime * 5, 6);
}
var isTokenValid = verifyAuthToken();
verifyDriveConnection(function (driveConnected)
{
console.log('--------------------- ' + currentCount + ' ---------------------');
console.log(' Delay: ' + saveDelay);
console.log(' Modified: ' + lastModification);
console.log(' IsOnline: ' + browserOnline);
console.log(' DriveConn: ' + driveConnected);
console.log(' TokenValid: ' + isTokenValid);
console.log(' IsOpen: ' + !isClosed);
if (saveDelay > VerificationTime && driveConnected && !isClosed && browserOnline && isTokenValid)
{
console.error('!!! ERROR: Local document not reconnected to remote server. Scenario done.');
setReproStep(7);
}
});
if (lastModification > ModificationTime && saveDelay === 0)
{
makeDriveDocChange();
}
}
function onSaveStateChange(e)
{
}
function verifyAuthToken()
{
var isValid = false;
var token = gapi.auth.getToken();
if (token)
{
var expireTime = parseInt(token.expires_at) * 1000;
if (Date.now() < expireTime)
{
isValid = true;
}
}
return isValid;
}
function makeDriveDocChange()
{
testMap.set('testData', ++docDataCounter);
lastWrite = Date.now();
}
function verifyDriveConnection(cb)
{
gapi.client.drive.about.get({
}).execute(function (res)
{
cb(res && !res.error);
});
}
function onDocLoadError(e)
{
console.log('Doc Load Error: ', e);
findAndLoadDoc();
}
Related
In a Chrome Extension, I have no problem adding, updating, and removing data to/from an IndexedDB database accessed by my service worker with Chrome Runtime Messaging sent from my content script. My trouble is doing a full table read from my content script. I do a console.log() to dump out the property before I send it back in my sendResponse in the Chrome Runtime Messaging, and I see the data there properly, but the content script receives an undefined. I assume this is because of the asynchronous nature of getting the data. I tried promises and async/await and the combination thereof and I just can't seem to get anything except an undefined in my content script on the message back from the service worker. I also ran sending a test array back and that worked just fine -- but receiving the IndexedDB table data does not work in the message passing. I also tried to JSONify the data and that didn't help either. What's the catch?
service-worker.js
importScripts('modules/idb.js');
var SW = {};
SW.onReady = function(){
chrome.runtime.onMessage.addListener(function(o, sender, sendResponse) {
(o.readTable) && sendResponse(SW.readTable(o,sender));
});
};
SW.readTable = function(o,sender){
var sTable = o.table;
new Promise((resolve) => {
IDB.readTable(sTable,function(asEntries){
resolve(asEntries);
});
}).then((asEntries) => {
console.log('SW asEntries',asEntries); // this shows me valid data in tests
var o = {};
// can also change this to fake data with asEntries being a string array and bug goes away in content.js
o.response = asEntries;
return o;
});
};
SW.onReady();
modules/idb.js
var IDB = {};
// Requires storage (or, even better, unlimitedStorage) permission in your manifest.json file.
// Note also that dev console of service worker will not show data -- have to use toolbar button popup panel (if you have one) and
// dev console from there, or code to access it, which sucks.
IDB.connectStore = function(sTable,sReadWriteSetting,fn){
var conn = indexedDB.open('unlimitedStorage', 1);
conn.onupgradeneeded = function(e) {
var db = e.target.result;
db.createObjectStore(sTable);
};
conn.onsuccess = function(e) {
var db = e.target.result;
var tx = db.transaction(sTable,sReadWriteSetting);
var store = tx.objectStore(sTable);
fn(db,tx,store);
};
};
IDB.addToTable = function(sTable,sKey,sVal){
IDB.connectStore(sTable,'readwrite',function(db,tx,store){
if ((sKey === undefined) || (sKey === '') || (sKey === null) || (sKey === false)) { // auto key by increment
var req = store.count();
req.onsuccess = function(e){
sKey = e.target.result + 1;
store.add(sVal,sKey);
tx.complete;
}
} else {
store.add(sVal,sKey);
tx.complete;
}
});
};
IDB.removeFromTable = function(sTable,sKey){
IDB.connectStore(sTable,'readwrite',function(db,tx,store){
store.delete(sKey);
tx.complete;
});
};
IDB.readTableByKey = function(sTable,sKey,fn){
IDB.connectStore(sTable,'readonly',function(db,tx,store){
var req = store.get(sKey);
req.onerror = function(e){
fn(e.target.result);
}
req.onsuccess = function(e){
fn(e.target.result);
}
});
};
IDB.readTable = function(sTable,fn){
IDB.connectStore(sTable,'readonly',function(db,tx,store){
var req = store.getAll();
req.onerror = function(e){
fn(e.target.result);
}
req.onsuccess = function(e){
fn(e.target.result);
}
});
};
content.js
var CONTENT = {};
CONTENT.onReady = function(){
var o = {};
o.readTable = true;
o.table = 'loadTimes';
chrome.runtime.sendMessage(o,function(response){
if (response.response) { // errors here with response property being undefined
console.log('CONTENT RCVD asEntries',response.response);
}
});
};
CONTENT.onReady();
Chrome extensions API, unlike Firefox WebExtensions, can't handle Promise returned from a callback or provided in sendResponse, https://crbug.com/1185241.
There's also a bug in your readTable: you need to add return before new Promise((resolve)
The solution is two-fold:
Use return true from the callback to allow asynchronous sendResponse
Call sendReponse inside .then of a Promise chain.
chrome.runtime.onMessage.addListener(function(o, sender, sendResponse) {
if (o.readTable) {
SW.readTable(o,sender).then(sendResponse);
return true;
} else {
sendResponse(); // Chrome 99-101 bug workaround, https://crbug.com/1304272
}
});
Do not use this answer. It is here for posterity reasons and is just a workaround. The chosen solution works.
The fix is to return data in a different message thread:
In the service worker in SW.readTable(), just return variable o with o.response = true and then ignore the response in the content script.
Before returning the variable o from SW.readTable(), do a chrome.runtime.sendMessage({readTableResult = true, data: asEntries},function(response){ /* ignore response */});
In the content script, ignore any response back from the readTable message. So, the if (response.response) {...} condition can be eliminated.
In the content script, add a message listener with chrome.runtime.onMessage.addListener(o, sender, sendResponse) and look for the condition (o.readTableResult). Once received, the o.data will now contain the asEntries data.
I'm a beginner in the Web Development world. I want to pair a BLE device (NRF52840 DK) with my mobile device through a web page. I tested an example for Web Bluetooth and it works fine on my PC as you can see on the following image:
Pairing my PC (Chrome) with a BLE device successfully
I did it through the popular extension Live Server on VS Code. When I tried to access to that page on my mobile (Android) using my IP and port, and pressed the button nothing happened.
Pairing my mobile (Chrome) with a BLE device unsuccessfully
Is there something that I'm not taking into consideration?
Here is the HTML & JS code:
<!DOCTYPE html>
<html>
<head>
<title>BLE WebApp</title>
</head>
<body>
<form>
<button>Connect with BLE device</button>
</form>
<script>
var deviceName = 'Nordic_HRM'
function isWebBluetoothEnabled() {
if (!navigator.bluetooth) {
console.log('Web Bluetooth API is not available in this browser!')
return false
}
return true
}
function getDeviceInfo() {
let chosenHeartRateService = null;
console.log('Requesting Bluetooth Device...')
navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => {
console.log("Getting HR Serviceā¦")
return server.getPrimaryService('heart_rate');
})
.then(service => {
chosenHeartRateService = service;
return Promise.all([
service.getCharacteristic('heart_rate_measurement')
.then(handleHeartRateMeasurementCharacteristic),
]);
})
}
function handleHeartRateMeasurementCharacteristic(characteristic) {
return characteristic.startNotifications()
.then(char => {
characteristic.addEventListener('characteristicvaluechanged',
onHeartRateChanged);
});
}
function onHeartRateChanged(event) {
const characteristic = event.target;
console.log(parseHeartRate(characteristic.value));
}
function parseHeartRate(data) {
const flags = data.getUint8(0);
const rate16Bits = flags & 0x1;
const result = {};
let index = 1;
if (rate16Bits) {
result.heartRate = data.getUint16(index, /*littleEndian=*/true);
index += 2;
} else {
result.heartRate = data.getUint8(index);
index += 1;
}
const contactDetected = flags & 0x2;
const contactSensorPresent = flags & 0x4;
if (contactSensorPresent) {
result.contactDetected = !!contactDetected;
}
const energyPresent = flags & 0x8;
if (energyPresent) {
result.energyExpended = data.getUint16(index, /*littleEndian=*/true);
index += 2;
}
const rrIntervalPresent = flags & 0x10;
if (rrIntervalPresent) {
const rrIntervals = [];
for (; index + 1 < data.byteLength; index += 2) {
rrIntervals.push(data.getUint16(index, /*littleEndian=*/true));
}
result.rrIntervals = rrIntervals;
}
return result;
}
document.querySelector('form').addEventListener('submit', function(event) {
event.stopPropagation()
event.preventDefault()
if (isWebBluetoothEnabled()) {
getDeviceInfo()
}
})
</script>
</body>
</html>
I don't think it's possible in the way you describe it.
Web Bluetooth only works on pages which has HTTPS enabled. It works on localhost too for testing purposes. Since you access the web application via an IP-address which doesn't have HTTPS enabled, Web Bluetooth is not available.
https://web.dev/bluetooth/#https-only
You can test this by adding a text to the page with a boolean or other text which will show if web bluetooth is available.
function isWebBluetoothEnabled() {
if (!navigator.bluetooth) {
console.log('Web Bluetooth API is not available in this browser!')
document.getElementById('bluetoothState').innerText = 'Not available'
return false
}
document.getElementById('bluetoothState').innerText = 'Available'
return true
}
use mkcert to create a certificate
usage:
mkcert 127.0.0.1 localhost 0.0.0.0 192.168.0.X
use the files generated to add it to settings.json file in .vscode directory (create if doesn't exist)
{
"liveServer.settings.https": {
"enable": true,
"cert": "/full/path/to/file/192.168.0.108+3.pem",
"key": "/full/path/to/file/192.168.0.108+3-key.pem",
"passphrase": ""
}
}
this will create a warning once due to the nature of self signed certificates, but it'll make sure almost all features of https work.
The issue I faced using this method is that it is considered not secure enough to install a PWA on a local device by chrome.
I'm currently working on an electron app that is essentially a DDNS launcher for a media server I control. Basically, it checks for an internet connection, gets the current IP for the server, then opens it in the system's default browser. However, the splash screen that I wrote is totally broken.
Whenever I launch the app on my system (using npm from the terminal), it loads the frame, but the image freezes loading at about the 1/3 point. It won't load the rest of the image until the script that it at the bottom of the main HTML page is finished executing.
Is there something I'm missing about this? I can provide excerpts of the code if needed.
EDIT:
Source code excerpt:
<script>
function wait(ms) {
var start = new Date().getTime();
var end = start;
while (end < start + ms) {
end = new Date().getTime();
}
}
const isOnline = require('is-online');
const isReachable = require('is-reachable');
const {
shell
} = require('electron');
window.onload = function() {
// Main Script
console.log('before');
wait(3000);
document.getElementById('progresstext').innerHTML = "Testing connection...";
bar.animate(0.15); // Number from 0.0 to 1.0
wait(250);
var amIOnline = false;
if (isOnline()) {
amIOnline = true;
}
console.log("Internet Test Ran");
if (!amIOnline) {
document.getElementById('errortext').innerHTML = "ERROR: No internet connection. Check the internet connection.";
document.getElementById('progresstext').innerHTML = "ERROR";
}
var isEmbyReachable = false;
if (isReachable('******')) {
isEmbyReachable = true;
document.getElementById('progresstext').innerHTML = "Connection Test: Passed";
//=> true
}
//Open Emby in the default browser
if (amIOnline && isEmbyReachable) {
shell.openExternal("*****");
}
};
</script>
Pastebin link to the full source: https://pastebin.com/u1iZeSSK
Thanks
Development System Specs: macOS Mojave 10.14, Latest stable build of electron
The problem is in your wait function, since node js is sigle threaded your wait function is blocking your process. You may try following code. But I really recommend you to take a look at how to write async functions in JavaScript and setInterval and setTimeout as a start.
But for the time you may try this code.
window.onload = function () {
// Main Script
console.log('before');
// wait 3 seconds
setTimeout(function () {
document.getElementById('progresstext').innerHTML = "Testing connection...";
bar.animate(0.15); // Number from 0.0 to 1.0
// wait 250 mills
setTimeout(function () {
var amIOnline = false;
if (isOnline()) {
amIOnline = true;
}
console.log("Internet Test Ran");
if (!amIOnline) {
document.getElementById('errortext').innerHTML = "ERROR: No internet connection. Check the internet connection.";
document.getElementById('progresstext').innerHTML = "ERROR";
}
var isEmbyReachable = false;
if (isReachable('******')) {
isEmbyReachable = true;
document.getElementById('progresstext').innerHTML = "Connection Test: Passed";
//=> true
}
//Open Emby in the default browser
if (amIOnline && isEmbyReachable) {
shell.openExternal("*****");
}
}, 250)
}, 3000)
};
You may not while or any other blocking loops to wait in JavaScript since it will block all other executions including page rendering.
I'm currently learning ReactJS and I decided to create a simple application.
The stack is:
React
Redux
React-router
DexieJS (IndexedDB)
The application is working. The problem is that when I try to test it on Firefox or incognito mode (in Chrome), I get this error:
TypeError: Cannot read property 'apply' of undefined
Anyone knows why I get this error and how I could handle that? I found that IndexedDB is not available in Firefox and incognito mode, so I tried to make a simple check:
if(!window.indexedDB) {
alert('Indexed DB is not supported by your browser. If you are running in incognito mode, please use the normal mode.')
}
But this is not working, I get the error again.
Here is the Github repo if you want to see the whole code:
https://github.com/Webd01/BM
Thanks for your help!
IndexedDB works fine in Chrome incognito mode, so if you have a problem there, it might be caused my something else.
But you are right that IndexedDB is not good in Firefox private browsing mode, although you're wrong about specifically how. window.indexedDB is not null in Firefox private browsing mode, but it does give you an error on upgradeneeded. I use something like this to detect it (this has some other browser compatibility checks too):
var checkIDB = function () {
if (typeof window.indexedDB === "undefined") {
console.log("IndexedDB not supported at all!");
return;
}
try {
keyRange.only([1]);
} catch (e) {
console.log("Buggy Microsoft IndexedDB implementation");
return;
}
var openRequest = window.indexedDB.open('firefox-private-test', 1);
openRequest.onerror = function (evt) {
console.error(evt.target.error);
if (evt.target.error.message.includes("aborted")) {
console.log("Some other error, maybe quota related:");
console.log(evt.target.error);
} else {
console.log("Firefox private mode, probably:");
console.log(evt.target.error);
}
}
openRequest.onupgradeneeded = function (evt) {
var db = evt.target.result;
var one = db.createObjectStore('one', {
autoIncrement: true,
keyPath: 'key'
});
one.createIndex('one', 'one');
one.add({one: 1});
var two = db.createObjectStore('two', {
autoIncrement: true,
keyPath: 'key'
});
two.createIndex ('two', 'two');
two.add({two: 2});
};
openRequest.onsuccess = function (evt) {
var db = evt.target.result;
var transaction;
try {
transaction = db.transaction(['one', 'two'], 'readwrite');
} catch (e) {
console.log("Some browser failed here, maybe an old version of Safari, I forget");
console.log(e.target.error);
return;
}
var count = 0;
transaction.objectStore('one').index('one').openCursor().onsuccess = function (evt) {
cursor = evt.target.result;
if (cursor) {
count += 1;
cursor.continue();
}
};
transaction.oncomplete = function () {
db.close();
if (count === 1) {
console.log("All good!");
} else {
console.log("Buggy Safari 10 IndexedDB implementation")
}
};
};
};
Method Windows.Devices.Enumeration.DeviceInformation.findAllAsync returns Collection of DeviceInformation. This object has property name assigned as the Device Bluetooth Name. But instead, the property contains values such as HM-12, SPP Dev or SerialPort which I assume are names of Bluetooth protocols.
Below is the example. Please note that this code was working properly prior Windows upgrade (which version caused this is unknown)
Working on Windows 10 mobile 10.0.14393.67
Windows 10 desktop outputs correct results.
var rfcomm = Windows.Devices.Bluetooth.Rfcomm;
var sockets = Windows.Networking.Sockets;
var streams = Windows.Storage.Streams;
var deviceInfo = Windows.Devices.Enumeration.DeviceInformation;
var cordova = require('cordova');
module.exports = {
connService: null,
connSocket: null,
connWriter: null,
connReader: null,
connDevice: null,
list: function(successCallback, errorCallback) {
setTimeout(function() {
try {
var selector =
rfcomm.RfcommDeviceService.getDeviceSelector(
rfcomm.RfcommServiceId.serialPort);
var parsedDevices = [];
deviceInfo.findAllAsync(selector, null).then(function(devices) {
if (devices.length > 0) {
for (var i = 0; i < devices.length; i++) {
parsedDevices.push({
id: devices[i].id,
name: devices[i].name
})
successCallback(parsedDevices);
}
} else {
errorCallback("No devices found.");
}
}, function(error) {
errorCallback({
error: "list",
message: error.message
});
});
} catch (ex) {
errorCallback(ex);
}
}, 0);
}
}
Value of selector (Windows 10 mobile):
System.Devices.DevObjectType:=10 AND System.Devices.AepService.ProtocolId:="{E0CBF06C-CD8B-4647-BB8A-263B43F0F974}" AND System.Devices.AepService.ServiceClassId:="{B142FC3E-FA4E-460B-8ABC-072B628B3C70}" AND System.Devices.AepService.Bluetooth.ServiceGuid:="{00001101-0000-1000-8000-00805F9B34FB}" AND System.Devices.AepService.ParentAepIsPaired:=System.StructuredQueryType.Boolean#True
Value of selector (Windows 10 PC) - works OK
System.Devices.InterfaceClassGuid:=\"{B142FC3E-FA4E-460B-8ABC-072B628B3C70}\" AND System.DeviceInterface.Bluetooth.ServiceGuid:=\"{00001101-0000-1000-8000-00805F9B34FB}\" AND System.Devices.InterfaceEnabled:=System.StructuredQueryType.Boolean#True
Problem
Your selector is only looking for devices/services that match a serialPort description, which is why you are getting names like SPP Dev or Serial Port.
Solution
Taken from the Windows 10 sample for BluetoothRFcommChat, to find Bluetooth devices use the following AQS query:
// Any extra properties you need
var requestedProperties = null
// The magic happens here, "e0cbf06c-cd8b-4647-bb8a-263b43f0f974" is
// the GUID for any Bluetooth device.
var selector = "(System.Devices.Aep.ProtocolId:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\")"
// Then run findAllAsync with these new filters
deviceInfo.findAllAsync(selector, requestedProperties).then(function(devices) {
// Your code here
}