var local_storage = document.getElementById("local_storage");
var session_storage = document.getElementById("session_storage");
var session_storage = document.getElementById("session_storage");
function storageClickCounter() {
if(typeof(Storage) !== "undefined") {
if (localStorage.clickcount) {
localStorage.clickcount = Number(localStorage.clickcount)+1;
} else {
localStorage.clickcount = 1;
}
if (sessionStorage.clickcount) {
sessionStorage.clickcount = Number(sessionStorage.clickcount)+1;
} else {
sessionStorage.clickcount = 1;
}
local_storage.innerHTML = "localStorage: " + localStorage.clickcount;
session_storage.innerHTML = "sessionStorage: " + sessionStorage.clickcount;
} else {
local_storage.innerHTML = "Sorry, your browser does not support web storage...";
}
}
function storageClickCounterReset() {
if(typeof(Storage) !== "undefined") {
localStorage.clickcount = 0;
sessionStorage.clickcount = 0;
local_storage.innerHTML = "localStorage have no clicks registered";
session_storage.innerHTML = "sessionStorage have no clicks registered";
} else {
local_storage.innerHTML = "Sorry, your browser does not support web storage...";
}
}
;(function () {
if(typeof(Storage) !== "undefined") {
if (localStorage.clickcount) {
local_storage.innerHTML = "localStorage: " + localStorage.clickcount;
} else {
local_storage.innerHTML = "localStorage have no clicks registered";
}
if (sessionStorage.clickcount) {
session_storage.innerHTML = "sessionStorage: " + sessionStorage.clickcount;
} else {
session_storage.innerHTML = "sessionStorage have no clicks registered";
}
} else {
local_storage.innerHTML = "Sorry, your browser does not support web storage...";
}
})();
db_name:
db_store:
obj_key:
var idb_result = document.getElementById("idb_result"),
db,
db_name = document.getElementById("db_name"),
db_store = document.getElementById("db_store"),
obj_key = document.getElementById("obj_key");
var rnd_str = () => {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 3; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
var rnd_int = () => (Math.floor(Math.random() * 1000) + 1);
function initDB() {
idb_result.innerHTML = '';
window.indexedDB = window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB;
// you may need references to some window.IDB* objects:
window.IDBTransaction = window.IDBTransaction ||
window.webkitIDBTransaction ||
window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange ||
window.webkitIDBKeyRange ||
window.msIDBKeyRange;
if(!window.indexedDB) {
idb_result.innerHTML += 'Unable to init IndexDB objects !!!';
}
// open database
var DBOpenRequest = window.indexedDB.open(db_name.value); // .open(db_name, 3)
DBOpenRequest.onblocked = function() {
idb_result.innerHTML += 'Database is open somewhere else (onblocked)';
};
DBOpenRequest.onerror = function(event) {
idb_result.innerHTML += 'Error loading database (onerror)';
};
DBOpenRequest.onsuccess = (event) => {
db = DBOpenRequest.result; // save connection
db.onerror = function(event) {
idb_result.innerHTML += 'Database error: '+event.target.errorCode+'';
};
idb_result.innerHTML += 'Database ['+db.name+'] [v.'+db.version+'] initialised, stores: ';
for (store of db.objectStoreNames) {
idb_result.innerHTML += '['+store+'] ';
}
idb_result.innerHTML += '';
};
// create or update db
DBOpenRequest.onupgradeneeded = (event) => {
var db = DBOpenRequest.result;
db.onerror = function(event) {
idb_result.innerHTML += 'Error loading database (onerror)';
};
db.onabort = function() {
idb_result.innerHTML += 'Error loading database (onabort)';
};
db.onversionchange = function(event) {
idb_result.innerHTML += 'Database change has occurred (onversionchange)';
if (event.newVersion == 2) {
db.deleteObjectStore(db_store.value); // only inside upgrade and versionchange event
}
};
db.onclose = function(event) {
idb_result.innerHTML += 'Database [' + db.name + '] has (unexpectedly?) closed (onclose)';
};
// database doesnt exist yet, the value of oldVersion is 0
if (event.oldVersion < 1) {
// Create an objectStore for this database
var objectStore = db.createObjectStore(db_store.value, { keyPath: "title" });
// define what data items the objectStore will contain
objectStore.createIndex("title", "title", { unique: false });
objectStore.createIndex("listeners", "listeners", { unique: false });
var initial_albums = [
{
title:"any_title",
listeners:5345
}
];
initial_albums.forEach(function(customer) {
objectStore.add(customer);
});
}
// // MIGRATIONS, consecutive schema update
// if (event.oldVersion < 2) {
// // version 2 introduces a new index of books by year.
// var bookStore = DBOpenRequest.transaction.objectStore("books");
// var yearIndex = bookStore.createIndex("by_year", "year");
// var del_index = objectStore.deleteIndex("contact");
// // ... version 3 introduces a new object store for magazines with two indexes.
// var magazines = db.createObjectStore("magazines");
// var publisherIndex = magazines.createIndex("by_publisher", "publisher");
// var frequencyIndex = magazines.createIndex("by_frequency", "frequency");
// // ... version 4
// db.createObjectStore("names", { autoIncrement : true });
// // ... version 5
// db.deleteObjectStore("names"); // only inside upgrade and versionchange event
// }
};
}
function get_transaction (t_mode = "readwrite") {
if(!db) return;
// transaction = db.transaction(db.objectStoreNames); // all stores
// specific store
// opens an object store that we can then manipulate the data inside of
// var objectStore = db.transaction('toDoList').objectStore('toDoList');
var transaction = db.transaction([db_store.value], t_mode);
// report on the success of opening the transaction
transaction.oncomplete = function(event) {
idb_result.innerHTML += 'Transaction completed';
};
transaction.onerror = function(event) {
idb_result.innerHTML += 'Transaction not opened due to error: '+transaction.error+'';
};
return transaction;
}
function closeDB() {
if(!db) return;
idb_result.innerHTML = '';
var db_close = db.close();
db=undefined;
idb_result.innerHTML += '['+db_name.value+'] database closed';
}
function clearStore() {
if(!db) return;
idb_result.innerHTML = '';
var store_clear = get_transaction().objectStore(db_store.value).clear();
store_clear.onsuccess = function(event) {
idb_result.innerHTML += '['+db_store.value+'] store cleared successfully';
};
store_clear.onsuccess = function(event) {
idb_result.innerHTML += 'Error clearing ['+db_store.value+'] store';
};
}
function deleteDB() {
idb_result.innerHTML = '';
closeDB();
var db_del = window.indexedDB.deleteDatabase(db_name.value);
db_del.onerror = function(event) {
idb_result.innerHTML += 'Error deleting ['+db_name.value+'] database';
};
db_del.onsuccess = function(event) {
idb_result.innerHTML += 'Database ['+db_name.value+'] deleted successfully';
};
}
function deleteStore() {
idb_result.innerHTML = '';
// call higher version to delete inside version change event
window.indexedDB.open(db_name.value, 2);
}
function addData() {
if(!db) return;
idb_result.innerHTML = '';
var objectStore = get_transaction().objectStore(db_store.value);
var data_obj = {
title:rnd_str(),
listeners:rnd_int()
// ,blob: xhr.response
};
var objectStore_request = objectStore.add(data_obj);
objectStore_request.onsuccess = function(event) {
// report the success of the request (this does not mean the item
// has been stored successfully in the DB - for that you need transaction.onsuccess)
idb_result.innerHTML += 'Data added: ';
for (x in data_obj) {
idb_result.innerHTML += x+': '+data_obj[x]+' ';
}
idb_result.innerHTML += '';
};
}
function getByKey() {
if(!db) return;
idb_result.innerHTML = '';
get_transaction()
.objectStore(db_store.value)
.get(obj_key.value)
.onsuccess = function(event) {
if(!event.target.result) {
idb_result.innerHTML += 'Nothing found !';
return;
}
idb_result.innerHTML += 'Data by key: ';
for (x in event.target.result) {
idb_result.innerHTML += x+': '+event.target.result[x]+' ';
}
idb_result.innerHTML += '';
};
}
function deleteByKey() {
if(!db) return;
idb_result.innerHTML = '';
get_transaction()
.objectStore(db_store.value)
.delete(obj_key.value)
.onsuccess = function(event) {
idb_result.innerHTML += 'Item with key '+obj_key.value+' deleted';
};
}
function updateByKey() {
if(!db) return;
idb_result.innerHTML = '';
var objectStore = get_transaction().objectStore(db_store.value);
objectStore
.get(obj_key.value)
.onsuccess = function(event) {
var data = event.target.result;
data.listeners = data.listeners + 1000
objectStore.put(data).onsuccess = function() {
idb_result.innerHTML += 'Added 1000 listeners to '+data.title+'';
};
};
}
function countItems() {
if(!db) return;
idb_result.innerHTML = '';
get_transaction()
.objectStore(db_store.value)
.count()
.onsuccess = function(event) {
idb_result.innerHTML += event.target.result+' items';
}
}
function dataViaCursor(in_range = false) {
if(!db) return;
idb_result.innerHTML = '';
if(in_range) {
var keyRangeValue = IDBKeyRange.bound("A", "T", true, true);
console.log([
keyRangeValue.includes('W'), // if in range
keyRangeValue.lower,
keyRangeValue.upper,
keyRangeValue.lowerOpen,
keyRangeValue.upperOpen
]);
}
// // only "Donna"
// IDBKeyRange.only("Donna");
// // anything past "Bill", including "Bill"
// IDBKeyRange.lowerBound("Bill");
// // anything past "Bill", but don't include "Bill"
// IDBKeyRange.lowerBound("Bill", true);
// // anything up to, but not including, "Donna"
// IDBKeyRange.upperBound("Donna", true);
// // anything between "Bill" and "Donna", but not including "Donna"
// IDBKeyRange.bound("Bill", "Donna", false, true);
var objectStore = get_transaction().objectStore(db_store.value);
if(in_range) {
// in range in reverse order
var osc = objectStore.openCursor(keyRangeValue,'prev');
}
else {
// objectStore.openCursor(null,'prev');
// nextunique, prevunique
var osc = objectStore.openCursor();
}
osc.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
for (x in cursor.value) {
idb_result.innerHTML += x+': '+cursor.value[x]+' ';
}
idb_result.innerHTML += '';
// // Abort the transaction we just did
// objectStore.transaction.abort();return;
console.log([
cursor.key,
cursor.value,
cursor.primaryKey,
cursor.source,
cursor.direction
]);
cursor.continue();
// cursor.advance(2);
} else {
idb_result.innerHTML += 'All entries displayed';
}
};
console.log([
// transaction.db,
// transaction.mode,
// transaction.objectStoreNames,
objectStore.indexNames,
objectStore.keyPath,
objectStore.name, // available to rename
objectStore.transaction,
objectStore.autoIncrement
]);
// var request = objectStore.openKeyCursor();
// request.onsuccess = function(event) {
// var cursor = event.target.result;
// if(cursor) {
// // cursor.key contains the key of the current record being iterated through
// // note that there is no cursor.value, unlike for openCursor
// // this is where you'd do something with the result
// cursor.continue();
// } else {
// // no more results
// }
// };
// let today = new Date();
// let yesterday = new Date(today);
// yesterday.setDate(today.getDate() - 1);
// let request = store.getKey(IDBKeyRange(yesterday, today));
// request.onsuccess = (event) => {
// let when = event.target.result;
// alert("The 1st activity in last 24 hours was occurred at " + when);
// };
}
function dataViaIndex() {
if(!db) return;
idb_result.innerHTML = '';
var transaction = get_transaction('readonly');
var objectStore = transaction.objectStore(db_store.value);
var myIndex = objectStore.index('title');
console.log([
myIndex.isAutoLocale,
myIndex.locale,
myIndex.name,
myIndex.objectStore,
myIndex.keyPath,
myIndex.multiEntry,
myIndex.unique
]);
myIndex.count().onsuccess = function(event) {
idb_result.innerHTML += event.target.result+' items';
}
myIndex.getAllKeys().onsuccess = function(event) {
idb_result.innerHTML += 'all keys: '+event.target.result+'';
}
// myIndex.myIndex.get('Bungle')
// myIndex.getKey('Bungle')
// myIndex.getAll()
// let count = 0;
// let unreadList = [];
myIndex.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if(cursor) {
// modify item while iterating
if (cursor.value.title === 'AAA') {
// // update
// const updateData = cursor.value;
// updateData.year = 2050;
// const request = cursor.update(updateData).onsuccess = function() {};
// // delete
// var request = cursor.delete().onsuccess = function() {};
};
// let lastPrimaryKey = getLastIteratedArticleId();
// if (lastPrimaryKey > cursor.primaryKey) {
// cursor.continuePrimaryKey("javascript", lastPrimaryKey);
// return;
// }
// // update lastIteratedArticleId
// setLastIteratedArticleId(cursor.primaryKey);
// // preload 5 articles into the unread list;
// unreadList.push(cursor.value);
// if (++count < 5) {
// cursor.continue();
// }
for (x in cursor.value) {
idb_result.innerHTML += x+': '+cursor.value[x]+' ';
}
idb_result.innerHTML += '';
cursor.continue();
} else {
idb_result.innerHTML += 'All entries displayed';
}
};
}
Key Path (keyPath ) |
Key Generator (autoIncrement ) |
Description |
---|---|---|
No | No | This object store can hold any kind of value, even primitive values like numbers and strings. You must supply a separate key argument whenever you want to add a new value. |
Yes | No | This object store can only hold JavaScript objects. The objects must have a property with the same name as the key path. |
No | Yes | This object store can hold any kind of value. The key is generated for you automatically, or you can supply a separate key argument if you want to use a specific key. |
Yes | Yes | This object store can only hold JavaScript objects. Usually a key is generated and the value of the generated key is stored in the object in a property with the same name as the key path. However, if such a property already exists, the value of that property is used as key rather than generating a new key. |
self
property returns the specialized scope for each context
var w_result = document.getElementById("w_result");
var dw;
function startDWorker() {
if(!!window.Worker) { // or: typeof(Worker) !== "undefined"
if(!dw) { dw = new Worker("js/worker-dedicated.js"); }
dw.onmessage = function(e) {
w_result.innerHTML = "dedicated worker: "+e.data+" "+w_result.innerHTML;
};
dw.port.onerror = function(e) {
w_result.innerHTML = "dedicated worker: ERROR !";
};
} else {
w_result.innerHTML += "Sorry, your browser does not support dedicated Worker...";
}
}
function sendToDWorker() {
if(dw) {
dw.postMessage(Math.floor(Math.random() * 1000) + 1);
}
}
function stopDWorker() {
dw.terminate(); // free browser/computer resources
// if you set the worker variable to undefined,
// after it has been terminated, you can reuse the code
dw = undefined;
}
var sw;
function startSWorker() {
if(!!window.SharedWorker) { // or: typeof(Worker) !== "undefined"
if(!sw) {
sw = new SharedWorker("js/worker-shared.js", "sharedWorker");
}
sw.port.onmessage = function(e) {
w_result.innerHTML = "shared worker: "+e.data+" [lastEventId:"+e.lastEventId+"] "+w_result.innerHTML;
};
sw.onerror = function(e) {
w_result.innerHTML = "shared worker: ERROR !";
sw.port.close();
};
// sw.port.start(); // required when using: port.addEventListener('message',...
} else {
w_result.innerHTML += "Sorry, your browser does not support SharedWorker...";
}
}
function sendToSWorker() {
if(sw) {
sw.port.postMessage(utils_randomInt());
}
}
function stopSWorker() {
if(sw) {
sw.port.close();
}
sw = undefined;
}
// one Worker is created for each logical processor reported by the browser
// and a record is created which includes a reference to the new worker
// as well as a Boolean value indicating whether or not we're using that worker yet;
// these objects are, in turn, stored into an array for later use.
// This creates a pool of workers we can use to process requests later
let workerList = [];
for (let i = 0; i < window.navigator.hardwareConcurrency; i++) {
let newWorker = {
worker: new Worker('cpuworker.js'),
inUse: false
};
workerList.push(newWorker);
}
var nr = 1, d = new Date();
function timedCount() {
postMessage("pulse " + (nr++)); // post a message back to the HTML page
setTimeout(timedCount, 1000);
}
timedCount();
self.onmessage = function(e) {
postMessage("["+d.toLocaleDateString()+" "+d.toLocaleTimeString()+"], owner message: "+e.data);
}
self.onoffline = function() {
postMessage("worker is now OFFLINE");
}
self.ononline = function() {
postMessage("worker is now ONLINE");
}
onconnect = function(e) {
importScripts("utils.js");
// importScripts("pusher.worker.js");
var client_port = e.ports[0],
nr = 1,
d = new Date();
clients = [];
clients.push(client_port); // fill list of worker clients (windows,tabs,...)
// client_port.start();
// // Connect to Pusher
// var pusher = new Pusher('1fb94680701ab31a3139', {
// encrypted: true
// });
// // Subscribe to test_channel
// var pusherChannel = pusher.subscribe('test_channel');
// bind to 'my_event' on pusherChannel
// pusherChannel.bind('my_event', function(data) {
// // Relay the payload on to each client_port
// clients.forEach(function(client_port){
// client_port.postMessage(data);
// });
// });
function timedCount() {
// self.name
client_port.postMessage("pulse " + (nr++)); // post a message back to the HTML page
setTimeout(timedCount, 1000);
}
timedCount();
client_port.onmessage = function(e) {
client_port.postMessage("["+
d.toLocaleDateString()+" "+d.toLocaleTimeString()+
"], owner message: "+e.data+
", own message: "+utils_randomInt()+
" pulse "+(nr++)
);
}
client_port.onerror = function(e) {
client_port.postMessage("["+d.toLocaleDateString()+" "+d.toLocaleTimeString()+"], ERROR ! ");
}
client_port.onoffline = function() {
client_port.postMessage("worker is now OFFLINE");
}
client_port.ononline = function() {
client_port.postMessage("worker is now ONLINE");
}
}
// var canvas = document.getElementById('myCanvas'),
// ctx = canvas.getContext('2d'),
// image = new Image();
// image.onload = function() {
// Promise.all([
// createImageBitmap(image, 0, 0, 32, 32),
// createImageBitmap(image, 32, 0, 32, 32)
// ]).then(function(sprites) {
// ctx.drawImage(sprites[0], 0, 0);
// ctx.drawImage(sprites[1], 32, 32);
// });
// }
// image.src = 'sprites.png';
var sw_result = document.getElementById("sw_result");
var sw_images = document.getElementById("sw_images");
var sw_base = document.getElementById("sw_base");
var sw_link = document.getElementById("sw_link");
var sw_list = document.getElementById("sw_list");
var serw;
if ('serviceWorker' in navigator) {
setInterval(()=>{
sw_list.innerHTML = "";
if (navigator.serviceWorker.controller) {
sw_list.innerHTML += 'page is controlled by:'+
navigator.serviceWorker.controller.scriptURL;
} else {
sw_list.innerHTML += 'page is not controlled by a service worker';
}
navigator.serviceWorker.getRegistrations().then(function(registrations){
if(registrations.length) sw_list.innerHTML += "ServiceWorkerRegistrations found:";
registrations.forEach((item, index, arr) => {
var sw = item.installing || item.waiting || item.active;
sw_list.innerHTML += "<button onclick=swUnregister('"+item.scope+
"')>unregister "+item.scope+" ["+sw.state+"]</button>";
});
});
},1000);
navigator.serviceWorker.oncontrollerchange = function(e) {
sw_list.innerHTML += 'page is controlled by: '+e.target.scriptURL+'';
};
navigator.serviceWorker.addEventListener('message', function(event){
sw_result.innerHTML += "serviceWorker all clients message:"+event.data;
if (event.ports[0]) {
event.ports[0].postMessage("Hello back! to serviceWorker");
}
});
} else {
sw_result.innerHTML += 'current browser doesnt support serviceWorker';
}
function swUnregister (scope = "") {
sw_result.innerHTML = "";
navigator.serviceWorker.getRegistrations().then(function(registrations){
registrations.forEach((item, index, arr) => {
if(item.scope == scope){
item.unregister().then(function(boolean) {
if(boolean){
sw_result.innerHTML += item.scope+" unregister is OK";
}
else{
sw_result.innerHTML += item.scope+" unregister with ERROR";
}
});
}
});
});
}
function installServiceWorker() {
if ('serviceWorker' in navigator) {
sw_result.innerHTML = "";
navigator.serviceWorker.register(
sw_link.value // +"?v="+Math.random()
// ,{ scope: sw_base.value+'/' }
// ,{ scope: 'http://web-engineer-book' }
// ,{ scope: sw_base.value }
// ,{ scope: './' }
).then(function(reg) {
if (reg.installing) {
sw_result.innerHTML += 'installing serviceWorker';
serw = reg.installing;
} else if (reg.waiting) {
sw_result.innerHTML += 'serviceWorker installed';
serw = reg.waiting;
} else if (reg.active) {
sw_result.innerHTML += 'serviceWorker active';
serw = reg.active;
}
if (serw) {
// reg.pushManager.getSubscription().then(function(subscription) {
// if (!subscription) {
// sw_result.innerHTML += 'Not yet subscribed to Push';
// return;
// }
// var mySubscription = subscription.toJSON();
// // initialize status, which includes setting UI elements for subscribed status
// // and updating Subscribers list via push
// var endpoint = subscription.endpoint;
// var key = subscription.getKey('p256dh');
// var auth = subscription.getKey('auth');
// subscription.unsubscribe().then(function(successful) {
// // You've successfully unsubscribed
// }).catch(function(e) {
// // Unsubscription failed
// })
// // Enable any UI which subscribes/unsubscribes from
// // push messages.
// var pushButton = document.querySelector('.js-push-button');
// pushButton.disabled = false;
// if (!subscription) {
// // We aren’t subscribed to push, so set UI
// // to allow the user to enable push
// return;
// }
// // Keep your server in sync with the latest subscriptionId
// sendSubscriptionToServer(subscription);
// showCurlCommand(subscription);
// // Set your UI to show they have subscribed for
// // push messages
// pushButton.textContent = 'Disable Push Messages';
// isPushEnabled = true;
// }).catch(function(err) {
// sw_result.innerHTML += 'Error during getSubscription()';
// });
sw_result.innerHTML += 'serviceWorker state: '+serw.state+'';
sw_result.innerHTML += 'serviceWorker scriptURL: '+serw.scriptURL+'';
serw.onstatechange = function(event) {
sw_result.innerHTML += 'serviceWorker state changed: '+event.target.state+'';
};
// // same as serw.onstatechange
// reg.onupdatefound = function() {
// sw_result.innerHTML += 'new service worker is being installed, scope: '+reg.scope+'';
// };
}
}).catch(function(error) {
sw_result.innerHTML += 'Registration failed with ' + error+'';
});
// this.onpush = function(event) {
// sw_result.innerHTML += 'onpush event.data: '+event.data+'';
// }
// navigator.serviceWorker.ready.then( function(reg) {
// sw_result.innerHTML += 'serviceWorker is active: '+reg.active+'';
// // reg.PushManager.supportedContentEncodings
// var options = {
// userVisibleOnly: true,
// applicationServerKey: 'qwerty654321'
// };
// reg.pushManager.subscribe(options).then(
// function(sbs) {
// // push subscription details
// sw_result.innerHTML += 'pushSubscription.subscriptionId: '+sbs.subscriptionId+'';
// sw_result.innerHTML += 'pushSubscription.endpoint: '+sbs.endpoint+'';
// sw_result.innerHTML += 'pushSubscription.expirationTime: '+sbs.expirationTime+'';
// sw_result.innerHTML += 'pushSubscription.options: '+sbs.options+'';
// }, function(error) {
// // report information about errors back to the application server
// sw_result.innerHTML += 'pushSubscription.error: '+error+'';
// }
// );
// });
}
}
function msgServiceWorker() {
navigator.serviceWorker.controller.postMessage("Client 1 message 1");
return new Promise(function(resolve, reject){
var msg_channel = new MessageChannel();
msg_channel.port1.onmessage = function(event){
if(event.data.error) {
sw_result.innerHTML += "messageChannel:"+event.data.error;
// reject(event.data.error);
} else {
sw_result.innerHTML += "messageChannel:"+event.data;
// resolve(event.data);
}
};
// Send message to service worker along with port for reply
navigator.serviceWorker.controller.postMessage("Client 1 message 2", [msg_channel.port2]);
});
}
function listCache() {
sw_result.innerHTML = "CACHES:";
caches_obj = {};
caches.keys().then(
cacheNames => cacheNames
).then( cacheNames =>{
cacheNames.forEach( cname => {
caches.open(cname).then( cache => {
cache.keys().then(ks => {
caches_obj[cname]=ks.map(req=>req.url)
});
});
});
});
setTimeout(()=>{
for (cname in caches_obj) {
sw_result.innerHTML +=
"<button onclick=\"removeCache('"+cname+"')\">"
+cname+" (remove)</button>";
caches_obj[cname].forEach((url) => {
sw_result.innerHTML += url;
});
}
},500);
}
function removeCache(key) {
sw_result.innerHTML = "";
deleted = "";
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cname => {
if(cname==key) {
return caches.delete(cname);
}
})
).then( ok => {
sw_result.innerHTML += "[ "+key+" ] cache deleted";
}, err => {
sw_result.innerHTML += "unable to remove cache ! "+err;
});
})
}
function addImage(url) {
// var myImage = document.createElement("img");
// myImage.setAttribute("height","100px");
// myImage.src = sw_base.value+url
// sw_result.appendChild(myImage);
// sw_result.innerHTML += '';
imgLoad(url).then(function(img) {
var myImage = document.createElement("img");
myImage.setAttribute("height","100px");
myImage.src = window.URL.createObjectURL(img);
sw_result.appendChild(myImage);
sw_result.innerHTML += '';
}, function(Error) {
sw_result.innerHTML += 'Error';
});
}
function imgLoad(link) {
// return a promise for an image loading
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', sw_base.value+link);
request.responseType = 'blob';
request.onload = function() {
if (request.status == 200) {
resolve(request.response);
} else {
reject(Error('Image didnt load successfully:' + request.statusText));
}
};
request.onerror = function() {
reject(Error('There was a network error'));
};
request.send();
});
}
function showNotification() {
Notification.requestPermission(function(result) {
if (result === 'granted') {
navigator.serviceWorker.ready.then(function(registration) {
registration.showNotification('Vibration Sample', {
body: 'Buzz! Buzz!',
vibrate: [200, 100, 200, 100, 200, 100, 200],
tag: 'vibration-sample',
icon: 'images/cellphone.jpg',
badge: 'images/cellphone.jpg',
image: 'images/cellphone.jpg',
dir: 'rtl',
data: 'random string as data '+Math.PI,
// actions:{action:str,title:str,icon:url},
// lang:en-US,
// renotify:false|true,
// requireInteraction:false|true,
});
var options = { tag : 'user_alerts' };
registration.getNotifications().then(function(notifications) {
sw_result.innerHTML = "Notifications:";
notifications.forEach((n)=>{
sw_result.innerHTML += "body: "+n.body;
sw_result.innerHTML += "data: "+n.data;
sw_result.innerHTML += "dir: "+n.dir;
sw_result.innerHTML += "icon: "+n.icon;
sw_result.innerHTML += "lang: "+n.lang;
sw_result.innerHTML += "tag: "+n.tag;
sw_result.innerHTML += "title: "+n.title;
sw_result.innerHTML += "-----------------------";
// sw_result.innerHTML += "onclick: "+n.onclick;
// sw_result.innerHTML += "onclose: "+n.onclose;
// sw_result.innerHTML += "onerror: "+n.onerror;
// sw_result.innerHTML += "onshow: "+n.onshow;
})
})
});
}
});
}
const CACHE_VERSION = 1;
const CURRENT_CACHES = {
prefetch: 'v' + CACHE_VERSION,
// other_cache: 'v' + OTHER_CACHE_VERSION
};
const cached_key='cached';
self.addEventListener('install', event => {
var now = Date.now();
var urlsToPrefetch = [
// 'index.html',
// './', // Alias for index.html
'images/wide.jpg',
'images/sw-lifecycle.png',
'images/rock.jpg',
'images/workplace.jpg',
'images/paris.jpg',
'images/mac.jpg'
];
event.waitUntil(
// caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
// return cache.addAll(urlsToPrefetch);
// });
caches.open(CURRENT_CACHES.prefetch).then(cache => {
var cachePromises = urlsToPrefetch.map(urlToPrefetch => {
var url = new URL(urlToPrefetch, location.href);
url.search += (url.search?'&':'?')+cached_key+'=' + now;
// if there is any chance that the resources
// are served off of a server that doesnt support CORS
var request = new Request(url,{mode:'no-cors'});
return fetch(request).then(response => {
if (response.status >= 400) {
throw new Error('request for ' + urlToPrefetch +
' failed with status ' + response.statusText);
}
// Use the original URL without the cache-busting parameter as the key for cache.put().
return cache.put(urlToPrefetch, response);
}).catch(error => {
console.error('Not caching ' + urlToPrefetch + ' due to ' + error);
});
});
return Promise.all(cachePromises).then(() => {
// console.log(CURRENT_CACHES.prefetch+' prefetched');
});
})
// .then(self.skipWaiting())
.catch(error => {
console.error('prefetching failed:', error);
})
// caches.has('v1').then(function(hasCache) {
// if (!hasCache) {
// someCacheSetupfunction();
// } else {
// caches.open('v1').then(function(cache) {
// return cache.addAll(myAssets);
// });
// }
// }).catch(function() {
// // Handle exception here.
// });
);
});
self.addEventListener('activate', event => {
event.waitUntil( async function() {
// clients loaded in the same scope do not need to be reloaded
// before their fetches will go through this service worker :
clients.claim();
// delete all caches that aren't named in CURRENT_CACHES
var expectedCacheNames = Object.values(CURRENT_CACHES);
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (expectedCacheNames.indexOf(cacheName) === -1) {
// If this cache name isn't present in the array of "expected" cache names, then delete it
console.log('deleting out of date cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
// // enable navigation preloads
// if (self.registration.navigationPreload) {
// await self.registration.navigationPreload.enable();
// }
}());
});
self.addEventListener('fetch', event => {
// avoid non-GET requests
if ( event.request.method != 'GET') return;
// && event.request.headers.get('accept').indexOf('text/html') !== -1
event.waitUntil(async function() {
// Exit early if we don't have access to the client.
// Eg, if it's cross-origin.
if (!event.clientId) return;
// Get the client.
const client = await clients.get(event.clientId);
// Exit early if we don't get the client.
// Eg, if it closed.
if (!client) return;
// Send a message to the client.
self.clients.matchAll().then(function (clients) {
clients.forEach(function(client) {
// client.postMessage(
// "Hey ["+event.clientId+"],
// serviceWorker is fetching or looking in cache...");
});
});
}());
event.respondWith(caches.match(event.request).then(r => {
// console.log( r ? 'cache: '+r.url : 'no cache, need to fetch...' );
return r || fetch(event.request).then(response => {
caches.open(CURRENT_CACHES.prefetch).then(cache => {
cache.put(event.request, response);
});
console.log('fetched: ', response.url);
return response.clone();
// return cache.put(event.request, response.clone()).then(() => {
// return response;
// });
}).catch(() => {
// return caches.match('/sw-test/gallery/myLittleVader.jpg');
console.error('no cache, fetching failed: ', error);
throw error;
});
}));
// // fallback response
// var response;
// event.respondWith(caches.match(event.request).catch(function() {
// return fetch(event.request);
// }).then(function(r) {
// response = r;
// caches.open('v1').then(function(cache) {
// cache.put(event.request, response);
// });
// return response.clone();
// }).catch(function() {
// return caches.match('/sw-test/gallery/myLittleVader.jpg');
// }));
});
function send_message_to_all_clients(msg){
clients.matchAll().then(clients => {
clients.forEach(client => {
send_message_to_client(client, msg).then(m => console.log(m));
})
})
}
function send_message_to_client(client, msg){
return new Promise(function(resolve, reject){
var msg_channel = new MessageChannel();
msg_channel.port1.onmessage = function(event){
if(event.data.error){
reject(event.data.error);
}else{
resolve(event.data);
}
};
client.postMessage("serviceWorker message: >>>"+msg+"<<<", [msg_channel.port2]);
});
}
self.addEventListener('message', event => {
if (event.ports[0]) { // client channel messaging
event.ports[0].postMessage("Hello back! to serviceWorker client ["+event.data+"]");
}
// response to any message
send_message_to_all_clients("serviceWorker received message: " + event.data);
});
self.addEventListener('notificationclick', function(event) {
event.notification.close();
send_message_to_all_clients("client notification data: " + event.notification.data);
self.registration.showNotification("google.com was opened (click to reopen)", {
actions: [{
action: "get",
title: "Get now.",
icon: 'images/cellphone.jpg'
}],
body: 'Buzz! Buzz!',
vibrate: [200, 100, 200, 100, 200, 100, 200],
tag: 'vibration-sample',
icon: 'images/cellphone.jpg',
badge: 'images/cellphone.jpg',
image: 'images/cellphone.jpg',
dir: 'ltr',
data: 'serviceWorker notification data'
// lang:en-US,
// renotify:false|true,
// requireInteraction:false|true,
// vibrate:pattern,
// data:data
});
// focus on domain window or open
event.waitUntil(
clients.matchAll({
type: "window"
}).then(function(clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (
client.url == 'https://www.google.com/' &&
'focus' in client
) {
if(!client.focused) return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow('https://www.google.com/');
}
}));
}
);
self.addEventListener('push', function(event) {
if (!(self.Notification && self.Notification.permission === 'granted')) {
return;
}
var data = {};
if (event.data) {
data = event.data.json();
// event.data.text();
// event.data.blob();
// event.data.arrayBuffer();
}
if(data.action === 'subscribe' || data.action === 'unsubscribe') {
var title = data.title || "Something Has Happened";
var message = data.message || "Here's something you might want to check out.";
// var icon = "../images/new-notification.png";
var notification = new Notification(title, {
body: message,
tag: 'simple-push-demo-notification'
// , icon: icon
});
notification.addEventListener('click', function() {
if (clients.openWindow) {
// clients.openWindow('https://example.blog.com/2015/03/04/something-new.html');
}
});
port.postMessage(data);
} else if(data.action === 'init' || data.action === 'chatMsg') {
port.postMessage(data);
}
event.waitUntil(
self.registration.showNotification('Yay a message.', {
body: 'We have received a push message.',
icon: '/images/icon-192x192.png',
tag: 'simple-push-demo-notification-tag'
})
);
// var dataInit = {
// data : 'Some sample text'
// }
// var myPushEvent = new PushEvent('push', dataInit);
// myPushEvent.data.text(); // should return 'Some sample text'
});
self.addEventListener('pushsubscriptionchange', function() {
// do something, usually resubscribe to push and
// send the new subscription details back to the
// server via XHR or Fetch
});
// inside main window/worker/frame
var channel = new MessageChannel();
var msg_frame = document.getElementById("msg_frame"); // target client
msg_frame.addEventListener("load", () => {
channel.port1.onmessage = (e) => {
sw_result.innerHTML += "iFrame message: "+e.data;
};
msg_frame.contentWindow.postMessage('Hello from the main page!', '*', [channel.port2]);
});
// target iframe
iframe
id="msg_frame"
scr="http://web-engineer-book/js-api-msg-iframe.html"
name="msg_frame"
style="margin-left:1em;width:98%;height:50px;overflow:auto;font-size:1em;border:3px dotted gray;"
/iframe
// inside client window/worker/frame
var output = document.querySelector('.output');
window.addEventListener('message', (e) => {
output.innerHTML = e.data;
// Use the transfered port to post a message back to the main frame
e.ports[0].postMessage('Message back from the IFrame');
});
// ----- WITH FETCH
class ProgressReportFetcher {
constructor(onProgress = function() {}) {
this.onProgress = onProgress;
}
// mimic native fetch() instantiation and return Promise
fetch(input, init = {}) {
const request = (input instanceof Request)? input : new Request(input)
this._cancelRequested = false;
return fetch(request, init).then(response => {
if (!response.body) {
throw Error('ReadableStream is not yet supported in this browser.')
}
// this occurs if cancel() was called before server responded (before fetch() Promise resolved)
if (this._cancelRequested) {
response.body.getReader().cancel();
return Promise.reject('cancel requested before server responded.');
}
if (!response.ok) {
// HTTP error server response
throw Error(`Server responded ${response.status} ${response.statusText}`);
}
// Server must send CORS header "Access-Control-Expose-Headers: content-length" to access
const contentLength = response.headers.get('content-length');
if (contentLength === null) {
// don't evaluate download progress if we can't compare against a total size
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Access-Control-Expose-Headers
throw Error('Content-Length server response header missing')
}
const total = parseInt(contentLength,10);
let loaded = 0;
this._reader=response.body.getReader()
const me = this;
return new Response(
new ReadableStream({
start(controller) {
if (me.cancelRequested) {
console.log('canceling read')
controller.close();
return;
}
read();
function read() {
me._reader.read().then(({done, value}) => {
if (done) {
// ensure onProgress called when content-length=0
if (total === 0) {
me.onProgress.call(me, {loaded, total});
}
controller.close();
return;
}
loaded += value.byteLength;
me.onProgress.call(me, {loaded, total});
controller.enqueue(value);
read();
}).catch(error => {
console.error(error);
controller.error(error)
});
}
}
})
)
});
}
cancel() {
console.log('download cancel requested.')
this._cancelRequested = true;
if (this._reader) {
console.log('cancelling current download');
return this._reader.cancel();
}
return Promise.resolve();
}
}
const imageLoader = (function() {
const loader = document.getElementById('loader');
const img = loader.querySelector('img');
const errorMsg = loader.querySelector('.error');
const loading = loader.querySelector('.progress-bar');
const progress = loader.querySelector('.progress');
let locked, started, progressFetcher, pct;
function downloadDone(url) {
console.log('downloadDone()')
img.src=url;
img.offsetWidth; // pre-animation enabler
loader.classList.remove('loading');
loader.classList.add('loading-complete');
// progressFetcher = null;
}
function startDownload() {
// Ensure "promise-safe" (aka "thread-safe" JavaScript).
// Caused by slow server response or consequetive calls to startDownload()
// before stopDownload() Promise resolves
if (locked) {
console.error('startDownload() failed. Previous download not yet initialized');
return;
}
locked = true;
stopDownload()
.then(function() {
locked = false;
progress.style.transform=`scaleX(0)`;
progress.offsetWidth; /* prevent animation when set to zero */
started = false;
pct = 0;
loader.classList.add('loading');
loader.classList.remove('loading-complete');
if (!progressFetcher) {
progressFetcher = new ProgressReportFetcher(updateDownloadProgress);
}
console.log('Starting download...');
progressFetcher.fetch(
'https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg')
.then(response => response.blob())
.then(blob => URL.createObjectURL(blob))
.then(url => downloadDone(url))
.catch(error => showError(error))
});
}
function stopDownload() {
// stop previous download
if (progressFetcher) {
return progressFetcher.cancel()
} else {
// no previous download to cancel
return Promise.resolve();
}
}
function showError(error) {
console.error(error);
loader.classList.remove('loading');
loader.classList.remove('loading-complete');
loader.classList.remove('loading-error');
errorMsg.offsetWidth; // pre-animation enabler
errorMsg.innerHTML = 'ERROR: '+ error.message;
loader.classList.add('loading-error');
}
function updateDownloadProgress({loaded, total}) {
if (!started) {
loader.classList.add('loading');
started = true;
}
// handle divide-by-zero edge case when Content-Length=0
pct = total? loaded/total : 1;
progress.style.transform=`scaleX(${pct})`;
// console.log('downloaded', Math.round(pct*100)+'%')
if (loaded === total) {
console.log('download complete')
}
}
return {
startDownload,
stopDownload
}
})()
imageLoader.startDownload();
// ----- WITH SERVICE WORKER
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
const scope = self.registration.scope;
// redirect index.html to service-worker-enabled page
if (event.request.url === scope || event.request.url === scope+'index.html') {
const newUrl = scope+'sw-installed.html';
console.log('respondWith', newUrl);
event.respondWith(fetch(newUrl))
} else if (progressIndicatorUrls.test(event.request.url)) {
console.log('VER',2,event.request.url)
event.respondWith(fetchWithProgressMonitor(event))
}
})
function fetchWithProgressMonitor(event) {
/* opaque request responses won't give us access to Content-Length and
* Response.body.getReader(), which are required for calculating download
* progress. Respond with a newly-constructed Request from the original Request
* that will give us access to those.
* See https://stackoverflow.com/questions/39109789/what-limitations-apply-to-opaque-responses
* 'Access-Control-Allow-Origin' header in the response must not be the
* wildcard '*' when the request's credentials mode is 'include'. We'll omit credentials in this demo.
*/
const newRequest = new Request(event.request, {
mode: 'cors',
credentials: 'omit'
})
return fetch(newRequest).then(response => respondWithProgressMonitor(event.clientId, response));
}
function respondWithProgressMonitor(clientId, response) {
if (!response.body) {
// See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream
console.warn("ReadableStream is not yet supported in this browser")
return response;
}
if (!response.ok) {
// HTTP error code response
return response;
}
const contentLength = response.headers.get('content-length');
if (contentLength == null) {
// don't track download progress if we can't compare against a total size
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Access-Control-Expose-Headers
console.warn('No Content-Length no header in response.');
return response;
}
let loaded = 0;
debugReadIterations=0; // direct correlation to server's response buffer size
const total = parseInt(contentLength,10);
const reader = response.body.getReader();
return new Response(
new ReadableStream({
start(controller) {
// get client to post message. Awaiting resolution first read() progress
// is sent for progress indicator accuracy
let client;
clients.get(clientId).then(c => {
client = c;
read();
});
function read() {
debugReadIterations++;
reader.read().then(({done, value}) => {
if (done) {
console.log('read()', debugReadIterations);
controller.close();
return;
}
controller.enqueue(value);
loaded += value.byteLength;
// console.log(' SW', Math.round(loaded/total*100)+'%');
dispatchProgress({client, loaded, total});
read();
})
.catch(error => {
// error only typically occurs if network fails mid-download
console.error('error in read()', error);
controller.error(error)
});
}
},
// Firefox excutes this on page stop, Chrome does not
cancel(reason) {
console.log('cancel()', reason);
}
})
)
}
function dispatchProgress({client, loaded, total}) {
client.postMessage({loaded,total})
}
<script src="../js/pako.min.js"></script>
<script src="../js/crc32.js"></script>
<script src="../js/png-lib.js"></script>
<script src="../js/png-transform-stream.js"></script>
<script src="../js/png-chunks.js"></script>
var streams_tests = document.getElementById("streams_tests");
function streamInImage (grayscale = false) {
streams_tests.innerHTML = "";
const image = document.createElement("img");
image.setAttribute("height","50px");
// Fetch the original image
fetch('images/gta5.png') // process rgb-alpha for conversion
// Retrieve its body as ReadableStream
.then(response => response.body)
.then(rs => {
if (grayscale) {
return rs.pipeThrough(new TransformStream(new GrayscalePNGTransformer()))
}
// read as stream
const reader = rs.getReader();
return new ReadableStream({
async start(controller) {
while (true) {
const { done, value } = await reader.read();
// When no more data needs to be consumed, break the reading
if (done) { break; }
// Enqueue the next data chunk into our target stream
controller.enqueue(value);
}
// Close the stream
controller.close();
reader.releaseLock();
}
})
})
// Create a new response out of the stream
.then(rs => new Response(rs))
// Create an object URL for the response
.then(response => response.blob())
.then(blob => URL.createObjectURL(blob))
// Update image
.then(url => {
image.src = url
streams_tests.appendChild(image);
}).catch(console.error);
}
var rss_result = "";
function randomStringStreamSTART () {
streams_tests.innerHTML = "";
result = "";
const stream = new ReadableStream({
start(controller) {
interval = setInterval(() => {
let string = randomChars();
// Add the string to the stream
controller.enqueue(string);
// show it on the screen
streams_tests.innerHTML += string;
}, 1000);
document.getElementById("rssc").addEventListener('click', function() {
clearInterval(interval);
// function teeStream() {
// const teedOff = stream.tee();
// readStream(teedOff[0], list2);
// readStream(teedOff[1], list3);
// }
// function readStream(stream, list) {
// readStream() - START
const reader = stream.getReader();
let charsReceived = 0;
// read() returns a promise that resolves
// when a value has been received
reader.read().then(function processText({ done, value }) {
// Result objects contain two properties:
// done - true if the stream has already given you all its data.
// value - some data. Always undefined when done is true.
if (done) {
streams_tests.innerHTML += "Stream complete: "+rss_result;
return;
}
charsReceived += value.length;
const chunk = value;
streams_tests.innerHTML += 'Read ' + charsReceived +
' characters so far. Current chunk = '+chunk;
rss_result += chunk;
// Read some more, and call this function again
return reader.read().then(processText);
});
// readStream() - END
controller.close();
})
},
pull(controller) {
// We don't really need a pull in this example
},
cancel() {
// This is called if the reader cancels,
// so we should stop generating strings
clearInterval(interval);
}
});
}
function randomChars() {
let string = "";
let choices = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()";
for (let i = 0; i < 8; i++) {
string += choices.charAt(Math.floor(Math.random() * choices.length));
}
return string;
}
function sendMessage(message) {
streams_tests.innerHTML = "";
const decoder = new TextDecoder("utf-8");
const queuingStrategy = new CountQueuingStrategy({ highWaterMark: 1 });
let result = "";
const writableStream = new WritableStream({
// Implement the sink
write(chunk) {
return new Promise((resolve, reject) => {
var buffer = new ArrayBuffer(2);
var view = new Uint16Array(buffer);
view[0] = chunk;
var decoded = decoder.decode(view, { stream: true });
streams_tests.innerHTML += "Chunk decoded: "+decoded;
result += decoded;
resolve();
});
},
close() { streams_tests.innerHTML += "[MESSAGE RECEIVED] " + result; },
abort(err) { streams_tests.innerHTML += "Sink error: " + err; }
}, queuingStrategy);
// defaultWriter is of type WritableStreamDefaultWriter
const defaultWriter = writableStream.getWriter();
const encoder = new TextEncoder();
const encoded = encoder.encode(message, { stream: true });
encoded.forEach((chunk) => {
defaultWriter.ready
.then(() => {
return defaultWriter.write(chunk);
})
.then(() => {
// streams_tests.innerHTML += "Chunk written to sink";
})
.catch((err) => {
streams_tests.innerHTML += "Chunk error: "+err;
});
});
// Call ready again to ensure that all chunks are written
// before closing the writer.
defaultWriter.ready
.then(() => {
defaultWriter.close();
})
.then(() => {
// streams_tests.innerHTML += "All chunks written";
})
.catch((err) => {
streams_tests.innerHTML += "Stream error: "+err;
});
}
function PngChunksStreamLog (img) {
streams_tests.innerHTML = "";
// Fetch the original image
fetch(img)
// Retrieve its body as ReadableStream
.then(response => response.body)
// Log each fetched Uint8Array chunk
.then(rs => logReadableStream('Fetch Response Stream', rs))
// Transform to a PNG chunk stream
.then(rs => rs.pipeThrough(new PNGTransformStream()))
// Log each transformed PNG chunk
.then(rs => logReadableStream('PNG Chunk Stream', rs))
}
class LogStreamSink {
constructor(name) {
this.name = name;
this.counter = 0;
}
write(chunk) {
this.counter += 1;
// console.log('Chunk %d of %s: %o', this.counter, this.name, chunk);
this.createRow(this.name, this.counter, chunk.constructor.name);
}
close() {
this.createRow(this.name, this.counter, 'Closed');
}
createRow(heading, col1, col2) {
streams_tests.innerHTML += heading+" "+col1+" "+col2;
}
}
function logReadableStream(name, rs) {
const [rs1, rs2] = rs.tee();
rs2.pipeTo(new WritableStream(new LogStreamSink(name))).catch(console.error);
return rs1;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>js13kGames A-Frame entries</title>
<meta name="description" content="A list ...">
<meta name="author" content="end3r">
<meta name="theme-color" content="#B12A34">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:image" content="icons/icon-512.png">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="style.css">
<link rel="manifest" href="js13kpwa.webmanifest">
<script src="data/games.js" defer></script>
<script src="app.js" defer></script>
</head>
<body>
<header>
<p>
<a class="logo" href="http://js13kgames.com">
<img src="img/js13kgames.png" alt="js13kGames">
</a>
</p>
</header>
<main>
<h1>js13kGames A-Frame entries</h1>
<p class="description">....</p>
<button id="notifications">notifications</button>
<section id="content">
// Content inserted in here
</section>
</main>
<footer>
<p>©....</p>
</footer>
</body>
</html>
var template = "<article>\n\
<img src='data/img/SLUG.jpg' alt='NAME'>\n\
<h3>#POS. NAME</h3>\n\
<ul>\n\
<li><span>Author:</span> <strong>AUTHOR</strong></li>\n\
<li><span>Twitter:</span> <a href='https://twitter.com/TWITTER'>@TWITTER</a></li>\n\
<li><span>Website:</span> <a href='http://WEBSITE/'>WEBSITE</a></li>\n\
<li><span>GitHub:</span> <a href='https://GITHUB'>GITHUB</a></li>\n\
</ul>\n\
</article>";
var content = '';
for(var i=0; i<games.length; i++) {
var entry = template.replace(/POS/g,(i+1))
.replace(/SLUG/g,games[i].slug)
.replace(/NAME/g,games[i].name)
.replace(/AUTHOR/g,games[i].author)
.replace(/TWITTER/g,games[i].twitter)
.replace(/WEBSITE/g,games[i].website)
.replace(/GITHUB/g,games[i].github);
entry = entry.replace('<a href=\'http:///\'></a>','-');
content += entry;
};
document.getElementById('content').innerHTML = content;
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('/pwa-examples/js13kpwa/sw.js');
};
var button = document.getElementById("notifications");
button.addEventListener('click', function(e) {
Notification.requestPermission().then(function(result) {
if(result === 'granted') {
randomNotification();
}
});
});
function randomNotification() {
var randomItem = Math.floor(Math.random()*games.length);
var notifTitle = games[randomItem].name;
var notifBody = 'Created by '+games[randomItem].author+'.';
var notifImg = 'data/img/'+games[randomItem].slug+'.jpg';
var options = {
body: notifBody,
icon: notifImg
}
var notif = new Notification(notifTitle, options);
setTimeout(randomNotification, 30000);
}
self.importScripts('data/games.js');
var cacheName = 'js13kPWA-v1';
var appShellFiles = [
'/pwa-examples/js13kpwa/',
'/pwa-examples/js13kpwa/index.html',
'/pwa-examples/js13kpwa/app.js',
'/pwa-examples/js13kpwa/style.css',
'/pwa-examples/js13kpwa/fonts/graduate.eot',
'/pwa-examples/js13kpwa/fonts/graduate.ttf',
'/pwa-examples/js13kpwa/fonts/graduate.woff',
'/pwa-examples/js13kpwa/favicon.ico',
'/pwa-examples/js13kpwa/img/js13kgames.png',
'/pwa-examples/js13kpwa/img/bg.png',
'/pwa-examples/js13kpwa/icons/icon-32.png',
'/pwa-examples/js13kpwa/icons/icon-64.png',
'/pwa-examples/js13kpwa/icons/icon-96.png',
'/pwa-examples/js13kpwa/icons/icon-128.png',
'/pwa-examples/js13kpwa/icons/icon-168.png',
'/pwa-examples/js13kpwa/icons/icon-192.png',
'/pwa-examples/js13kpwa/icons/icon-256.png',
'/pwa-examples/js13kpwa/icons/icon-512.png'
];
var gamesImages = [];
for(var i=0; i<games.length; i++) {
gamesImages.push('data/img/'+games[i].slug+'.jpg');
}
var contentToCache = appShellFiles.concat(gamesImages);
self.addEventListener('install', function(e) {
console.log('[Service Worker] Install');
e.waitUntil(
caches.open(cacheName).then(function(cache) {
console.log('[Service Worker] Caching all: app shell and content');
return cache.addAll(contentToCache);
})
);
});
self.addEventListener('activate', function(e) {
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if(cacheName.indexOf(key) === -1) {
return caches.delete(key);
}
}));
})
);
});
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(e.request).then(function(r) {
console.log('[Service Worker] Fetching resource: '+e.request.url);
return r || fetch(e.request).then(function(response) {
return caches.open(cacheName).then(function(cache) {
console.log('[Service Worker] Caching new resource: '+e.request.url);
cache.put(e.request, response.clone());
return response;
});
});
})
);
});
var games = [
{
slug: 'lost-in-cyberspace',
name: 'Lost in Cyberspace',
author: 'Zosia and Bartek',
twitter: 'bartaz',
website: '',
github: 'github.com/bartaz/lost-in-cyberspace'
},
{
slug: 'vernissage',
name: 'Vernissage',
author: 'Platane',
twitter: 'platane_',
website: 'github.com/Platane',
github: 'github.com/Platane/js13k-2017'
},
// ...
{
slug: 'emma-3d',
name: 'Emma-3D',
author: 'Prateek Roushan',
twitter: '',
website: '',
github: 'github.com/coderprateek/Emma-3D'
}
];
{
"name": "js13kGames Progressive Web App",
"short_name": "js13kPWA",
"description": "Progressive Web App that lists ...",
"icons": [
{
"src": "icons/icon-32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "icons/icon-64.png",
"sizes": "64x64",
"type": "image/png"
},
{
"src": "icons/icon-96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "icons/icon-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "icons/icon-168.png",
"sizes": "168x168",
"type": "image/png"
},
{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/pwa-examples/js13kpwa/index.html",
"scope": "/myapp/"
// "start_url": "http://web-engineer-book/index.html",
// "scope": "http://web-engineer-book/",
"display": "fullscreen",
"theme_color": "#B12A34",
"background_color": "#B12A34",
"lang": "en-US",
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=com.example.app1",
"id": "com.example.app1"
}, {
"platform": "itunes",
"url": "https://itunes.apple.com/app/example-app1/id123456789"
}],
"orientation": "landscape"
}
let deferredPrompt;
const install_button = document.getElementById('install_button');
install_button.style.display = 'none';
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
// e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// Update UI to notify the user they can add to home screen
install_button.style.display = 'block';
install_button.addEventListener('click', (e) => {
// hide our user interface that shows our A2HS button
install_button.style.display = 'none';
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
alert('User accepted the A2HS prompt');
} else {
alert('User dismissed the A2HS prompt');
}
deferredPrompt = null;
});
});
});
window.addEventListener("appinstalled", (ev) => {
const date = new Date(ev.timeStamp / 1000);
alert(`App got installed at ${date.toTimeString()}.`);
});
<img src='data/img/placeholder.png' data-src='data/img/SLUG.jpg' alt='NAME'>
article img[data-src] {
filter: blur(0.2em);
}
article img {
filter: blur(0em);
transition: filter 0.5s;
}
var imagesToLoad = document.querySelectorAll('img[data-src]');
var loadImages = function(image) {
image.setAttribute('src', image.getAttribute('data-src'));
image.onload = function() {
image.removeAttribute('data-src');
};
};
if('IntersectionObserver' in window) {
var observer = new IntersectionObserver(function(items, observer) {
items.forEach(function(item) {
if(item.isIntersecting) {
loadImages(item.target);
observer.unobserve(item.target);
}
});
});
imagesToLoad.forEach(function(img) {
observer.observe(img);
});
} else {
imagesToLoad.forEach(function(img) {
loadImages(img);
});
}
window.addEventListener('load', function() {
var status = document.getElementById("status");
var log = document.getElementById("log");
function updateOnlineStatus(event) {
var condition = navigator.onLine ? "online" : "offline";
status.className = condition;
status.innerHTML = condition.toUpperCase();
log.insertAdjacentHTML("beforeend", "Event: " + event.type + "; Status: " + condition);
}
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
});
var po_tests = document.getElementById("po_tests");
var po_tests_1 = document.getElementById("po_tests_1");
var po_tests_2 = document.getElementById("po_tests_2");
var prevRatio = 0.0; // to save visibility
var increasingColor = "rgba(67, 244, 65, ratio)";
var decreasingColor = "rgba(65, 145, 244, ratio)";
// var po_track_started = false;
var po_is_visible = false;
var po_view_start = 0;
var po_total_view = 0;
var po_data_1 = document.getElementById("po_data_1");
var po_data_2 = document.getElementById("po_data_2");
function startTracking() {
track_interval = setInterval(()=>{
let totalSeconds = po_total_view / 1000;
let sec = Math.floor(totalSeconds % 60);
let min = Math.floor(totalSeconds / 60);
po_data_1.innerHTML = po_data_2.innerHTML =
"po_is_visible: "+po_is_visible+
"po_total_view: "+min+":"+sec.toString().padStart(2,"0")+" ["+po_total_view+"ms]";
// "performance.now(): " + performance.now();
updateAdTimer();
},150);
}
function stopTracking() {
clearInterval(track_interval);
}
// window.addEventListener("load", function(event) {
var observer;
var options = {
root: null,
rootMargin: "0px",
threshold: buildThresholdList()
// [0.5],
// [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
// [0, 0.25, 0.5, 0.75, 1.0]
};
observer = new IntersectionObserver(
handleIntersect,
options
);
observer.observe(po_tests);
// }, false);
function buildThresholdList() {
var thresholds = [];
var numSteps = 20; // how many thresholds between 0.0 and 1.0
for (var i=1.0; i<=numSteps; i++) {
var ratio = i/numSteps;
thresholds.push(ratio);
}
thresholds.push(0);
return thresholds;
}
function handleIntersect(entries, observer) {
entries.forEach(function(entry) {
po_is_visible = false;
if (entry.isIntersecting) {
if (entry.intersectionRatio >= 0.35) {
po_view_start = performance.now();
// po_track_started = true;
po_is_visible = true;
}
}
po_tests_1.innerHTML =
"intersectionRatio: " + entry.intersectionRatio +
"isIntersecting: " + entry.isIntersecting +
"---------------------------------" +
"boundingClientRect.width: " + entry.boundingClientRect.width +
"boundingClientRect.height: " + entry.boundingClientRect.height +
"boundingClientRect.top: " + entry.boundingClientRect.top +
"boundingClientRect.right: " + entry.boundingClientRect.right +
"boundingClientRect.bottom: " + entry.boundingClientRect.bottom +
"boundingClientRect.left: " + entry.boundingClientRect.left +
"boundingClientRect.x: " + entry.boundingClientRect.x +
"boundingClientRect.y: " + entry.boundingClientRect.y;
po_tests_2.innerHTML =
"intersectionRect.width: " + entry.intersectionRect.width +
"intersectionRect.height: " + entry.intersectionRect.height +
"intersectionRect.top: " + entry.intersectionRect.top +
"intersectionRect.right: " + entry.intersectionRect.right +
"intersectionRect.bottom: " + entry.intersectionRect.bottom +
"intersectionRect.left: " + entry.intersectionRect.left +
"intersectionRect.x: " + entry.intersectionRect.x +
"intersectionRect.y: " + entry.intersectionRect.y +
"---------------------------------" +
"rootBounds.width: " + entry.rootBounds.width +
"rootBounds.height: " + entry.rootBounds.height +
"rootBounds.top: " + entry.rootBounds.top +
"rootBounds.right: " + entry.rootBounds.right +
"rootBounds.bottom: " + entry.rootBounds.bottom +
"rootBounds.left: " + entry.rootBounds.left +
"rootBounds.x: " + entry.rootBounds.x +
"rootBounds.y: " + entry.rootBounds.y;
if (entry.intersectionRatio > prevRatio) {
entry.target.style.backgroundColor =
increasingColor.replace("ratio", entry.intersectionRatio);
} else {
entry.target.style.backgroundColor =
decreasingColor.replace("ratio", entry.intersectionRatio);
}
prevRatio = entry.intersectionRatio;
let box = entry.target;
let visiblePct = (Math.floor(entry.intersectionRatio * 100)) + "%";
box.querySelector(".topLeft").innerHTML = visiblePct;
box.querySelector(".topRight").innerHTML = visiblePct;
box.querySelector(".bottomLeft").innerHTML = visiblePct;
box.querySelector(".bottomRight").innerHTML = visiblePct;
});
}
function updateAdTimer() {
if(!po_is_visible) return;
let lastStarted = po_view_start;
let currentTime = performance.now();
if (lastStarted) {
let diff = currentTime - lastStarted;
po_total_view = parseFloat(po_total_view) + diff;
}
po_view_start = currentTime;
}
document.addEventListener(
"visibilitychange",
() => {
if (document.hidden) {
po_is_visible = 0;
po_view_start = 0;
// videoElement.pause();
} else {
po_is_visible = 1;
// videoElement.play();
}
},
false
);
// videoElement.addEventListener("pause", function(){
// document.title = 'Paused';
// }, false);
// // When the video plays, set the title.
// videoElement.addEventListener("play", function(){
// document.title = 'Playing';
// }, false);
var mo_tests = document.getElementById("mo_tests");
const MutationObserver = window.MutationObserver ||
window.WebKitMutationObserver ||
window.MozMutationObserver ||
null;
if (!MutationObserver) {
battery_tests.innerHTML='MutationObserver not supported';
}
// target
const mo_test_object = document.querySelector("#mo_test_object");
// options
const mo_config = {
attributes: true,
childList: true,
characterData: true,
subtree: true,
};
// instance
const mo_observer = new MutationObserver(function(mutations) {
mo_tests.innerHTML = `mutations =` + mutations; // MutationRecord
mutations.forEach(function(mutation) {
// console.log("mutation =", mutation);
if (mutation.type === "characterData") {
// target & object === typeof(mutation.target)
// console.log(
// "A child node has been added OR removed.",
// mutation.target,
// typeof(mutation.target)
// );
// console.log("[...mutation.addedNodes].length", [...mutation.addedNodes].length);
// console.log("[...mutation.removedNodes].length", [...mutation.removedNodes].length);
// if (mutation.target && [...mutation.addedNodes].length) {
// // [...mutation.addedNodes].length
// console.log(`A child node ${mutation.target} has been added!`, mutation.target);
// }
// if (mutation.target && [...mutation.removedNodes].length) {
// // [...mutation.removedNodes].length
// console.log(`A child node ${mutation.target} has been removed!`, mutation.target);
// }
}
if (mutation.type === "childList") {
if (mutation.target && [...mutation.addedNodes].length) {
mo_tests.innerHTML += `A child node ${mutation.target} has been added!` + mutation.target;
}
if (mutation.target && [...mutation.removedNodes].length) {
mo_tests.innerHTML += `A child node ${mutation.target} has been removed!` + mutation.target;
}
// do somwthings
let list_values = [];
list_values = [].slice.call(mo_test_object.children).map(function(node) {
return node.innerHTML;
}).filter(function(str) {
if (str === "") {
return false;
} else {
return true;
}
});
mo_tests.innerHTML += list_values;
}
if (mutation.type === "attributes") {
mo_tests.innerHTML += "mutation = " + mutation;
mo_tests.innerHTML += `The \`${mutation.attributeName}\` attribute was modified.`;
// console.log("list style =", list.style);
let { width, height } = mo_test_object.style;
let style = { width, height };
mo_tests.innerHTML += "style = " + JSON.stringify(style, null, 4);
}
});
});
function startMO (){
mo_observer.observe(mo_test_object, mo_config);
mo_tests.innerHTML = "mo_observer.observe(mo_test_object, mo_config)";
}
function stopMO (){
mo_observer.disconnect();
mo_tests.innerHTML = "mo_observer.disconnect()";
}
function addRandomChild(){
var arc = document.createElement("span");
arc.innerHTML = utils_randomInt();
mo_test_object.appendChild(arc);
}
// var targetNode = document.querySelector("#someElement");
// var observerOptions = {
// childList: true,
// attributes: true,
// subtree: true, // omit or set to false to observe only changes to the parent node
// attributeOldValue: true,
// // watch for changes to the status and username attributes
// // in any elements contained within a subtree
// attributeFilter: ["status","username"]
// // characterDataOldValue: true
// // characterData: true
// }
// var observer = new MutationObserver(callback);
// observer.observe(targetNode, observerOptions);
// // handle any still-pending mutations
// var mutations = observer.takeRecords();
// if (mutations) {
// callback(mutations);
// }
// // observer.disconnect();
// function callback(mutationList, observer) {
// mutationList.forEach((mutation) => {
// switch(mutation.type) {
// case 'childList':
// /* One or more children have been added to and/or removed
// from the tree; see mutation.addedNodes and
// mutation.removedNodes */
// break;
// case 'attributes':
// "attributeName" + mutation.attributeName +
// " changed to " + mutation.target[attributeName] +
// " (was " + mutation.oldValue + ")"
// switch(mutation.attributeName) {
// case "status":
// userStatusChanged(mutation.target.username, mutation.target.status);
// break;
// case "username":
// usernameChanged(mutation.oldValue, mutation.target.username);
// break;
// }
// break;
// }
// });
And remember, don't do anything that affects anything, unless it turns out you were supposed to, in which case, for the love of God, don't not do it! Ow, my spirit! I don't want to be rescued. You guys aren't Santa! You're not even robots. I've got to find a way to escape the horrible ravages of youth. Suddenly, I'm going to the bathroom like clockwork, every three hours. And those jerks at Social Security stopped sending me checks. Now 'I' have to pay 'them'!
// change the font-size of a header and paragraph as a slider value is changed
// causing the containing div to change width
// shows that you can respond to changes in an element size,
// even if they have nothing to do with the viewport.
// checkbox turns the observer off and on: change/not in response to the div width changing
if(window.ResizeObserver) {
const h1Elem = document.querySelector('#ro-h1');
const pElem = document.querySelector('#ro-p');
const divElem = document.querySelector('#ro-div');
const slider = document.querySelector('#ro-chk');
const checkbox = document.querySelector('#ro-rng');
divElem.style.width = '600px';
slider.addEventListener('input', () => {
divElem.style.width = slider.value + 'px';
})
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
if(entry.contentBoxSize) {
// Checking for chrome as using a non-standard array
if (entry.contentBoxSize[0]) {
h1Elem.style.fontSize = Math.max(1.5, entry.contentBoxSize[0].inlineSize/200) + 'rem';
pElem.style.fontSize = Math.max(1, entry.contentBoxSize[0].inlineSize/600) + 'rem';
} else {
h1Elem.style.fontSize = Math.max(1.5, entry.contentBoxSize.inlineSize/200) + 'rem';
pElem.style.fontSize = Math.max(1, entry.contentBoxSize.inlineSize/600) + 'rem';
}
// canvas.width = entry.devicePixelContentBoxSize[0].inlineSize;
// canvas.height = entry.devicePixelContentBoxSize[0].blockSize;
} else {
h1Elem.style.fontSize = Math.max(1.5, entry.contentRect.width/200) + 'rem';
pElem.style.fontSize = Math.max(1, entry.contentRect.width/600) + 'rem';
}
}
console.log('Size changed');
});
resizeObserver.observe(divElem);
checkbox.addEventListener('change', () => {
if(checkbox.checked) {
resizeObserver.observe(divElem);
} else {
resizeObserver.unobserve(divElem);
}
});
} else {
console.log('Resize observer not supported!');
}
var battery_tests = document.getElementById("battery_tests");
if ('getBattery' in navigator) {
navigator.getBattery().then(monitorBattery);
} else {
battery_tests.innerHTML='Battery API not supported';
}
function monitorBattery(battery) {
batteryInfo(battery);
battery.addEventListener('levelchange', batteryInfo.bind(null, battery));
battery.addEventListener('chargingchange', batteryInfo.bind(null, battery));
battery.addEventListener('dischargingtimechange', batteryInfo.bind(null, battery));
battery.addEventListener('chargingtimechange', batteryInfo.bind(null, battery));
}
function batteryInfo(battery) {
battery_tests.innerHTML+="battery.level: "+(battery.level*100)+"%";
battery_tests.innerHTML+="battery.charging: "+(battery.charging?"Yes":"No");
battery_tests.innerHTML+="battery.chargingTime: "+battery.chargingTime;
battery_tests.innerHTML+="battery.dischargingTime: "+battery.dischargingTime;
battery_tests.innerHTML+="----------------------";
}
var bt_tests = document.getElementById("bt_tests");
// bt_characteristics, bt_start_notify_characteristics, bt_get_characteristics
var bt_characteristics = document.getElementById("bt_characteristics");
// deviceInfo, bt_characteristics, bt_start_notify_characteristics,
// bt_scan/bt_disconnect/bt_reconnect, bt_get_characteristics
var bt_services = document.getElementById("bt_services");
// deviceInfo, bt_scan/bt_disconnect/bt_reconnect
var bt_device_name = document.getElementById("bt_device_name");
// deviceInfo, bt_scan/bt_disconnect/bt_reconnect
var bt_device_name_prefix = document.getElementById("bt_device_name_prefix");
// deviceInfo
var bt_all_devices = document.getElementById("bt_all_devices");
// -----js/bt.js
// bt_valueToDeviceType
// bt_valueToUsbVendorName
// try {
// const device = await navigator.bluetooth.requestDevice(options);
if ('bluetooth' in navigator) {
// navigator.bluetooth.requestDevice(options);
} else {
bt_tests.innerHTML='Bluetooth API not supported';
}
function bt_device_info() {
let filters = [];
let filterService = document.querySelector('#bt_services').value;
if (filterService.startsWith('0x')) {
filterService = parseInt(filterService);
}
if (filterService) {
filters.push({services: [filterService]});
}
let filterName = document.querySelector('#bt_device_name').value;
if (filterName) {
filters.push({name: filterName});
}
let filterNamePrefix = document.querySelector('#bt_device_name_prefix').value;
if (filterNamePrefix) {
filters.push({namePrefix: filterNamePrefix});
}
let options = {};
if (document.querySelector('#bt_all_devices').checked) {
options.acceptAllDevices = true;
} else {
options.filters = filters;
}
bt_tests.innerHTML += "Requesting Bluetooth Device...";
bt_tests.innerHTML += 'with ' + JSON.stringify(options);
navigator.bluetooth.requestDevice(options)
.then(device => {
bt_tests.innerHTML += '> Name: ' + device.name;
bt_tests.innerHTML += '> Id: ' + device.id;
bt_tests.innerHTML += '> Connected: ' + device.gatt.connected;
})
.catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
function bt_battery_level() {
bt_tests.innerHTML = "Requesting Bluetooth Device...";
navigator.bluetooth.requestDevice(
{filters: [{services: ['battery_service']}]})
.then(device => {
bt_tests.innerHTML += "Connecting to GATT Server...";
return device.gatt.connect();
})
.then(server => {
bt_tests.innerHTML += "Getting Battery Service...";
return server.getPrimaryService('battery_service');
})
.then(service => {
bt_tests.innerHTML += "Getting Battery Level Characteristic...";
return service.getCharacteristic('battery_level');
})
.then(characteristic => {
bt_tests.innerHTML += "Reading Battery Level...";
return characteristic.readValue();
})
.then(value => {
let batteryLevel = value.getUint8(0);
bt_tests.innerHTML += '> Battery Level is ' + batteryLevel + "%";
})
.catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
function bt_characteristics() {
let serviceUuid = document.querySelector('#bt_services').value;
if (serviceUuid.startsWith('0x')) {
serviceUuid = parseInt(serviceUuid);
}
let characteristicUuid = document.querySelector('#bt_characteristics').value;
if (characteristicUuid.startsWith('0x')) {
characteristicUuid = parseInt(characteristicUuid);
}
bt_tests.innerHTML = "Requesting Bluetooth Device...";
navigator.bluetooth.requestDevice({filters: [{services: [serviceUuid]}]})
.then(device => {
bt_tests.innerHTML += "Connecting to GATT Server...";
return device.gatt.connect();
})
.then(server => {
bt_tests.innerHTML += "Getting Service...";
return server.getPrimaryService(serviceUuid);
})
.then(service => {
bt_tests.innerHTML += "Getting Characteristic...";
return service.getCharacteristic(characteristicUuid);
})
.then(characteristic => {
bt_tests.innerHTML += '> Characteristic UUID: ' +
characteristic.uuid;
bt_tests.innerHTML += '> Broadcast: ' +
characteristic.properties.broadcast;
bt_tests.innerHTML += '> Read: ' +
characteristic.properties.read;
bt_tests.innerHTML += '> Write w/o response: ' +
characteristic.properties.writeWithoutResponse;
bt_tests.innerHTML += '> Write: ' +
characteristic.properties.write;
bt_tests.innerHTML += '> Notify: ' +
characteristic.properties.notify;
bt_tests.innerHTML += '> Indicate: ' +
characteristic.properties.indicate;
bt_tests.innerHTML += '> Signed Write: ' +
characteristic.properties.authenticatedSignedWrites;
bt_tests.innerHTML += '> Queued Write: ' +
characteristic.properties.reliableWrite;
bt_tests.innerHTML += '> Writable Auxiliaries: ' +
characteristic.properties.writableAuxiliaries;
})
.catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
var bt_myCharacteristic;
function bt_start_notify_characteristics() {
let serviceUuid = document.querySelector('#service').value;
if (serviceUuid.startsWith('0x')) {
serviceUuid = parseInt(serviceUuid);
}
let characteristicUuid = document.querySelector('#characteristic').value;
if (characteristicUuid.startsWith('0x')) {
characteristicUuid = parseInt(characteristicUuid);
}
bt_tests.innerHTML = "Requesting Bluetooth Device...";
navigator.bluetooth.requestDevice({filters: [{services: [serviceUuid]}]})
.then(device => {
bt_tests.innerHTML += "Connecting to GATT Server...";
return device.gatt.connect();
})
.then(server => {
bt_tests.innerHTML += "Getting Service...";
return server.getPrimaryService(serviceUuid);
})
.then(service => {
bt_tests.innerHTML += "Getting Characteristic...";
return service.getCharacteristic(characteristicUuid);
})
.then(characteristic => {
bt_myCharacteristic = characteristic;
return bt_myCharacteristic.startNotifications().then(_ => {
bt_tests.innerHTML += "> Notifications started";
bt_myCharacteristic.addEventListener(
'characteristicvaluechanged',
bt_handleNotifications);
});
})
.catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
function bt_stop_notify_characteristics() {
if (bt_myCharacteristic) {
bt_myCharacteristic.stopNotifications()
.then(_ => {
bt_tests.innerHTML += "> Notifications stopped";
bt_myCharacteristic.removeEventListener(
'characteristicvaluechanged',
bt_handleNotifications);
})
.catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
}
function bt_handleNotifications(event) {
let value = event.target.value;
let a = [];
// Convert raw data bytes to hex values just for the sake of showing something.
// In the "real" world, you'd use data.getUint8, data.getUint16 or even
// TextDecoder to process raw data bytes.
for (let i = 0; i < value.byteLength; i++) {
a.push('0x' + ('00' + value.getUint8(i).toString(16)).slice(-2));
}
bt_tests.innerHTML += '> ' + a.join(' ');
}
var bluetoothDevice;
function bt_scan() {
let options = {filters: []};
let filterService = document.querySelector('#bt_services').value;
if (filterService.startsWith('0x')) {
filterService = parseInt(filterService);
}
if (filterService) {
options.filters.push({services: [filterService]});
}
let filterName = document.querySelector('#bt_device_name').value;
if (filterName) {
options.filters.push({name: filterName});
}
let filterNamePrefix = document.querySelector('#bt_device_name_prefix').value;
if (filterNamePrefix) {
options.filters.push({namePrefix: filterNamePrefix});
}
bluetoothDevice = null;
bt_tests.innerHTML = "Requesting Bluetooth Device...";
navigator.bluetooth.requestDevice(options)
.then(device => {
bluetoothDevice = device;
bluetoothDevice.addEventListener(
'gattserverdisconnected',
bt_onDisconnected
);
return bt_connect();
})
.catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
function bt_onDisconnected(event) {
// Object event.target is Bluetooth Device getting disconnected.
bt_tests.innerHTML += "> Bluetooth Device disconnected";
}
function bt_connect() {
bt_tests.innerHTML += "Connecting to Bluetooth Device...";
return bluetoothDevice.gatt.connect()
.then(server => {
bt_tests.innerHTML += "> Bluetooth Device connected";
});
}
function bt_disconnect() {
if (!bluetoothDevice) { return; }
bt_tests.innerHTML += "Disconnecting from Bluetooth Device...";
if (bluetoothDevice.gatt.connected) {
bluetoothDevice.gatt.disconnect();
} else {
bt_tests.innerHTML += "> Bluetooth Device is already disconnected";
}
}
function bt_reconnect() {
if (!bluetoothDevice) { return; }
if (bluetoothDevice.gatt.connected) {
bt_tests.innerHTML += "> Bluetooth Device is already connected";
return;
}
bt_connect().catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
function bt_get_characteristics() {
let serviceUuid = document.querySelector('#bt_services').value;
if (serviceUuid.startsWith('0x')) {
serviceUuid = parseInt(serviceUuid);
}
let characteristicUuid = document.querySelector('#bt_characteristics').value;
if (characteristicUuid.startsWith('0x')) {
characteristicUuid = parseInt(characteristicUuid);
}
bt_tests.innerHTML = "Requesting Bluetooth Device...";
navigator.bluetooth.requestDevice({filters: [{services: [serviceUuid]}]})
.then(device => {
bt_tests.innerHTML += "Connecting to GATT Server...";
return device.gatt.connect();
})
.then(server => {
bt_tests.innerHTML += "Getting Service...";
return server.getPrimaryService(serviceUuid);
})
.then(service => {
bt_tests.innerHTML += "Getting Characteristics...";
if (characteristicUuid) {
// Get all characteristics that match this UUID.
return service.getCharacteristics(characteristicUuid);
}
// Get all characteristics.
return service.getCharacteristics();
})
.then(characteristics => {
bt_tests.innerHTML += '> Characteristics: ' +
characteristics.map(c => c.uuid).join('\n' + ' '.repeat(19));
})
.catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
function bt_device_info_characteristics() {
bt_tests.innerHTML = "Requesting any Bluetooth Device...";
navigator.bluetooth.requestDevice({
// filters: [...] <- Prefer filters to save energy & show relevant devices.
acceptAllDevices: true,
optionalServices: ['device_information']})
.then(device => {
bt_tests.innerHTML += "Connecting to GATT Server...";
return device.gatt.connect();
})
.then(server => {
bt_tests.innerHTML += "Getting Device Information Service...";
return server.getPrimaryService('device_information');
})
.then(service => {
bt_tests.innerHTML += "Getting Device Information Characteristics...";
return service.getCharacteristics();
})
.then(characteristics => {
let queue = Promise.resolve();
let decoder = new TextDecoder('utf-8');
characteristics.forEach(characteristic => {
switch (characteristic.uuid) {
case BluetoothUUID.getCharacteristic('manufacturer_name_string'):
queue = queue.then(_ => characteristic.readValue()).then(value => {
bt_tests.innerHTML += '> Manufacturer Name String: ' + decoder.decode(value);
});
break;
case BluetoothUUID.getCharacteristic('model_number_string'):
queue = queue.then(_ => characteristic.readValue()).then(value => {
bt_tests.innerHTML += '> Model Number String: ' + decoder.decode(value);
});
break;
case BluetoothUUID.getCharacteristic('hardware_revision_string'):
queue = queue.then(_ => characteristic.readValue()).then(value => {
bt_tests.innerHTML += '> Hardware Revision String: ' + decoder.decode(value);
});
break;
case BluetoothUUID.getCharacteristic('firmware_revision_string'):
queue = queue.then(_ => characteristic.readValue()).then(value => {
bt_tests.innerHTML += '> Firmware Revision String: ' + decoder.decode(value);
});
break;
case BluetoothUUID.getCharacteristic('software_revision_string'):
queue = queue.then(_ => characteristic.readValue()).then(value => {
bt_tests.innerHTML += '> Software Revision String: ' + decoder.decode(value);
});
break;
case BluetoothUUID.getCharacteristic('system_id'):
queue = queue.then(_ => characteristic.readValue()).then(value => {
bt_tests.innerHTML += "> System ID: ";
bt_tests.innerHTML += '> Manufacturer Identifier: ' +
padHex(value.getUint8(4)) + padHex(value.getUint8(3)) +
padHex(value.getUint8(2)) + padHex(value.getUint8(1)) +
padHex(value.getUint8(0));
bt_tests.innerHTML += ' > Organizationally Unique Identifier: ' +
padHex(value.getUint8(7)) + padHex(value.getUint8(6)) +
padHex(value.getUint8(5));
});
break;
case BluetoothUUID.getCharacteristic('ieee_11073-20601_regulatory_certification_data_list'):
queue = queue.then(_ => characteristic.readValue()).then(value => {
bt_tests.innerHTML += '> IEEE 11073-20601 Regulatory Certification Data List: ' +
decoder.decode(value);
});
break;
case BluetoothUUID.getCharacteristic('pnp_id'):
queue = queue.then(_ => characteristic.readValue()).then(value => {
bt_tests.innerHTML += "> PnP ID:";
bt_tests.innerHTML += ' > Vendor ID Source: ' +
(value.getUint8(0) === 1 ? 'Bluetooth' : 'USB');
if (value.getUint8(0) === 1) {
bt_tests.innerHTML += ' > Vendor ID: ' +
(value.getUint8(1) | value.getUint8(2) << 8);
} else {
bt_tests.innerHTML += ' > Vendor ID: ' +
getUsbVendorName(value.getUint8(1) | value.getUint8(2) << 8);
}
bt_tests.innerHTML += ' > Product ID: ' +
(value.getUint8(3) | value.getUint8(4) << 8);
bt_tests.innerHTML += ' > Product Version: ' +
(value.getUint8(5) | value.getUint8(6) << 8);
});
break;
default: bt_tests.innerHTML += '> Unknown Characteristic: ' + characteristic.uuid;
}
});
return queue;
})
.catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
/* Utils */
function padHex(value) {
return ('00' + value.toString(16).toUpperCase()).slice(-2);
}
function getUsbVendorName(value) {
return value +
(value in bt_valueToUsbVendorName ? ' (' + bt_valueToUsbVendorName[value] + ')' : '');
}
function bt_gap_characteristics() {
bt_tests.innerHTML = "Requesting any Bluetooth Device...";
navigator.bluetooth.requestDevice({
// filters: [...] <- Prefer filters to save energy & show relevant devices.
acceptAllDevices: true,
optionalServices: ['generic_access']})
.then(device => {
bt_tests.innerHTML += "Connecting to GATT Server...";
return device.gatt.connect();
})
.then(server => {
bt_tests.innerHTML += "Getting GAP Service...";
return server.getPrimaryService('generic_access');
})
.then(service => {
bt_tests.innerHTML += "Getting GAP Characteristics...";
return service.getCharacteristics();
})
.then(characteristics => {
let queue = Promise.resolve();
characteristics.forEach(characteristic => {
switch (characteristic.uuid) {
case BluetoothUUID.getCharacteristic('gap.appearance'):
queue = queue.then(_ => readAppearanceValue(characteristic));
break;
case BluetoothUUID.getCharacteristic('gap.device_name'):
queue = queue.then(_ => readDeviceNameValue(characteristic));
break;
case BluetoothUUID.getCharacteristic('gap.peripheral_preferred_connection_parameters'):
queue = queue.then(_ => readPPCPValue(characteristic));
break;
case BluetoothUUID.getCharacteristic('gap.central_address_resolution_support'):
queue = queue.then(_ => readCentralAddressResolutionSupportValue(characteristic));
break;
case BluetoothUUID.getCharacteristic('gap.peripheral_privacy_flag'):
queue = queue.then(_ => readPeripheralPrivacyFlagValue(characteristic));
break;
default: bt_tests.innerHTML += '> Unknown Characteristic: ' + characteristic.uuid;
}
});
return queue;
})
.catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
function readAppearanceValue(characteristic) {
return characteristic.readValue().then(value => {
bt_tests.innerHTML += '> Appearance: ' +
getDeviceType(value.getUint16(0, true /* Little Endian */));
});
}
function readDeviceNameValue(characteristic) {
return characteristic.readValue().then(value => {
bt_tests.innerHTML += '> Device Name: ' + new TextDecoder().decode(value);
});
}
function readPPCPValue(characteristic) {
return characteristic.readValue().then(value => {
bt_tests.innerHTML += "Peripheral Preferred Connection Parameters:";
bt_tests.innerHTML += ' > Minimum Connection Interval: ' +
(value.getUint8(0) | value.getUint8(1) << 8) * 1.25 + 'ms';
bt_tests.innerHTML += ' > Maximum Connection Interval: ' +
(value.getUint8(2) | value.getUint8(3) << 8) * 1.25 + 'ms';
bt_tests.innerHTML += ' > Slave Latency: ' +
(value.getUint8(4) | value.getUint8(5) << 8) + 'ms';
bt_tests.innerHTML += ' > Connection Supervision Timeout Multiplier: ' +
(value.getUint8(6) | value.getUint8(7) << 8);
});
}
function readCentralAddressResolutionSupportValue(characteristic) {
return characteristic.readValue().then(value => {
let supported = value.getUint8(0);
if (supported === 0) {
bt_tests.innerHTML += "> Central Address Resolution: Not Supported";
} else if (supported === 1) {
bt_tests.innerHTML += "> Central Address Resolution: Supported";
} else {
bt_tests.innerHTML += "> Central Address Resolution: N/A";
}
});
}
function readPeripheralPrivacyFlagValue(characteristic) {
return characteristic.readValue().then(value => {
let flag = value.getUint8(0);
if (flag === 1) {
bt_tests.innerHTML += "> Peripheral Privacy Flag: Enabled";
} else {
bt_tests.innerHTML += "> Peripheral Privacy Flag: Disabled";
}
});
}
/* Utils */
function getDeviceType(value) {
return value +
(value in bt_valueToDeviceType ? ' (' + bt_valueToDeviceType[value] + ')' : '');
}
function bt_discover_services_and_characteristics() {
// Validate services UUID entered by user first.
let optionalServices = document.querySelector('#bt_services').value
.split(/, ?/).map(s => s.startsWith('0x') ? parseInt(s) : s)
.filter(s => s && BluetoothUUID.getService);
bt_tests.innerHTML = "Requesting any Bluetooth Device...";
navigator.bluetooth.requestDevice({
// filters: [...] <- Prefer filters to save energy & show relevant devices.
acceptAllDevices: true,
optionalServices: optionalServices})
.then(device => {
bt_tests.innerHTML += "Connecting to GATT Server...";
return device.gatt.connect();
})
.then(server => {
// Note that we could also get all services that match a specific UUID by
// passing it to getPrimaryServices().
bt_tests.innerHTML += "Getting Services...";
return server.getPrimaryServices();
})
.then(services => {
bt_tests.innerHTML += "Getting Characteristics...";
let queue = Promise.resolve();
services.forEach(service => {
queue = queue.then(_ => service.getCharacteristics().then(characteristics => {
bt_tests.innerHTML += '> Service: ' + service.uuid;
characteristics.forEach(characteristic => {
bt_tests.innerHTML += '>> Characteristic: ' + characteristic.uuid + ' ' +
getSupportedProperties(characteristic);
});
}));
});
return queue;
})
.catch(error => {
bt_tests.innerHTML += 'Argh! ' + error;
});
}
/* Utils */
function getSupportedProperties(characteristic) {
let supportedProperties = [];
for (const p in characteristic.properties) {
if (characteristic.properties[p] === true) {
supportedProperties.push(p.toUpperCase());
}
}
return '[' + supportedProperties.join(', ') + ']';
}
if ('usb' in navigator) {
// navigator.usb.getDevices();
} else {
usb_tests.innerHTML='USB API not supported';
}
var usb_tests = document.getElementById("usb_tests");
function usb_get_devices() {
usb_tests.innerHTML = "";
navigator.usb.getDevices().then(devices => {
usb_tests.innerHTML += "Total devices: " + devices.length;
devices.forEach(device => {
usb_tests.innerHTML += "Product name: " + device.productName + ", serial number " + device.serialNumber;
device.addEventListener("connect", (e) => {
usb_tests.innerHTML += "previously paired device is connected";
});
device.addEventListener("disconnect", (e) => {
usb_tests.innerHTML += "paired device is disconnected";
});
});
});
}
function usb_request_device() {
var rd_v = document.getElementById("rd_v").value; // vendorId
var rd_p = document.getElementById("rd_p").value; // productId
var rd_cc = document.getElementById("rd_cc").value; // classCode
var rd_sc = document.getElementById("rd_sc").value; // subclassCode
var rd_pc = document.getElementById("rd_pc").value; // protocolCode
var rd_sn = document.getElementById("rd_sn").value; // serialNumber
let filters = {};
rd_v?(filters.vendorId=rd_v):'';
rd_p?(filters.productId=rd_p):'';
rd_cc?(filters.classCode=rd_cc):'';
rd_sc?(filters.subclassCode=rd_sc):'';
rd_pc?(filters.protocolCode=rd_pc):'';
rd_sn?(filters.serialNumber=rd_sn):'';
usb_tests.innerHTML = "Request with: " + JSON.stringify(filters);
navigator.usb.requestDevice({filters: [
filters
// ,{vendorId: 0x1209, productId: 0xa850}
]})
.then(usbDevice => {
usb_tests.innerHTML += "Product name: " + usbDevice.productName;
})
.catch(e => {
usb_tests.innerHTML += "There is no device. " + e;
});
}
var device;
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(selectedDevice => {
device = selectedDevice;
return device.open(); // Begin a session.
})
.then(() => device.selectConfiguration(1)) // Select configuration #1 for the device.
.then(() => device.claimInterface(2)) // Request exclusive control over interface #2.
.then(() => device.controlTransferOut({
requestType: 'class',
recipient: 'interface',
request: 0x22,
value: 0x01,
index: 0x02})) // Ready to receive data
.then(() => device.transferIn(5, 64)) // Waiting for 64 bytes of data from endpoint #5.
.then(result => {
let decoder = new TextDecoder();
console.log('Received: ' + decoder.decode(result.data));
})
.catch(error => { console.log(error); });
// Third-party WebUSB Arduino library
#include <WebUSB.h>
WebUSB WebUSBSerial(1 /* https:// */, "webusb.github.io/arduino/demos");
#define Serial WebUSBSerial
void setup() {
Serial.begin(9600);
while (!Serial) {
; // Wait for serial port to connect.
}
Serial.write("WebUSB FTW!");
Serial.flush();
}
void loop() {
// Nothing here for now.
}
var media_tests = document.getElementById("media_tests");
var stream_video = document.getElementById("stream_video");
var tracks, a_track, v_track;
var front = false;
navigator.mediaDevices.ondevicechange = function(event) {
devicesList();
}
function devicesList() {
media_tests.innerHTML = "";
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
media_tests.innerHTML = "enumerateDevices() not supported";
return;
}
navigator.mediaDevices.enumerateDevices()
.then(function(devices) {
devices.forEach(function(device) {
let [kind,type,direction] = device.kind.match(/(\w+)(input|output)/i);
media_tests.innerHTML +=
"--------------------------------" +
"device.type: " + device.type +
"direction: " + direction +
"device.kind: " + device.kind +
"device.label: " + device.label +
"device.deviceId: " + device.deviceId +
"device.groupId: " + device.groupId;
});
}).catch(function(err) {
media_tests.innerHTML = err.name + ": " + error.message;
});
}
var constraints = window.constraints = {
video: {
width: { min: 640, ideal: 1920 },
height: { min: 400, ideal: 1080 },
aspectRatio: { ideal: 1.7777777778 },
facingMode: front?"user":"environment"
},
audio: true
// video: {
// width: 480,
// height: 320,
// frameRate: 30,
// facingMode: "user" // "environment"
// }
// audio: {
// sampleRate: 44100,
// sampleSize: 16,
// volume: 0.25,
// channelCount: 2
// }
// video: {
// width: { min: 1024, ideal: 1280, max: 1920 },
// height: { min: 776, ideal: 720, max: 1080 }
// }
// video: { facingMode: "user" }
// video: { facingMode: { exact: "environment" } }
};
function startVideoStream() {
navigator.mediaDevices.getUserMedia(constraints).then(stream => {
media_tests.innerHTML = "stream.id: " + stream.id;
stream_video.srcObject = stream;
// stream.onremovetrack = function(event) {
// label.innerHTML = "Removed: " + event.track.kind + ": " + event.track.label;
// };
// stream.onaddtrack = function(event) {
// label.innerHTML = event.track.kind + ": " + event.track.label;
// };
tracks = stream.getTracks();
let audioTracks = stream.getAudioTracks();
let videoTracks = stream.getVideoTracks();
if (audioTracks.length) { a_track = audioTracks[0]; }
if (videoTracks.length) { v_track = videoTracks[0]; }
// stream_video.onloadedmetadata = function(e) {
// stream_video.play();
// };
}).then(function(){
tracks.forEach(function(track) {
trackCapSett(track);
// MediaStreamTrack.clone();
});
}).catch(err => {
media_tests.innerHTML = err
});
}
function stopVideoStream() {
if (v_track) { v_track.stop(); }
if (a_track) { audioTrack.stop(); }
v_track = a_track = null;
videoElement.srcObject = null;
// let stream = stream_video.srcObject;
// let tracks = stream.getTracks();
// tracks.forEach(function(track) {
// track.stop();
// });
// stream_video.srcObject = null;
}
// change constraints on the fly
function switchCameras() {
front = !front;
constraints.video.facingMode = front?"user":"environment";
v_track.applyConstraints(constraints);
stopVideoStream();
startVideoStream();
// let stream = stream_video.srcObject;
// let tracks = stream.getTracks();
// tracks.forEach(function(track) {
// let stream_constraints = track.getConstraints();
// stream_constraints.facingMode = front?"user":"environment";
// track.applyConstraints(stream_constraints);
// });
// const video_constraints = {
// width: { min: 640, ideal: 1920, max: 1920 },
// height: { min: 400, ideal: 1080 },
// aspectRatio: 1.777777778,
// frameRate: { max: 30 },
// facingMode: { exact: "user" }
// advanced: [
// {width: 1920, height: 1280},
// {aspectRatio: 1.333}
// ]
// };
// const v_track = stream.getVideoTracks()[0];
// v_track.applyConstraints(video_constraints).then(() => {
// imageCapture = new ImageCapture(v_track);
// return imageCapture.getPhotoCapabilities();
// }).catch(e => {
// // The constraints could not be satisfied by the available devices.
// });
// // Stop stream after 5 seconds
// setTimeout(() => {
// // const tracks = mediaStream.getVideoTracks();
// const tracks = mediaStream.getAudioTracks();
// tracks[0].stop()
// }, 5000)
// stream.getTrackById("primary-audio-track").applyConstraints({ volume: 0.5 });
// stream.getTrackById("commentary-track").enabled = true;
}
function captureImage() {
media_tests.innerHTML="";
if(!v_track){media_tests.innerHTML="No track";return;}
imageCapture = new ImageCapture(v_track);
imageCapture.takePhoto().then(function(blob) {
var img = document.createElement("img");
img.setAttribute("height","100px");
img.src = URL.createObjectURL(blob);
media_tests.appendChild(img);
}).catch(function(error) {
media_tests.innerHTML = 'takePhoto() error: '+error;
});
imageCapture.getPhotoSettings()
.then(photoSettings => {
media_tests.innerHTML +=
"--------------------------------" +
"photoSettings.fillLightMode: " + photoSettings.fillLightMode +
"photoSettings.imageHeight: " + photoSettings.imageHeight +
"photoSettings.imageWidth: " + photoSettings.imageWidth +
"photoSettings.redEyeReduction: " + photoSettings.redEyeReduction;
return imageCapture.getPhotoCapabilities();
}).then(photoCapabilities => {
// redEyeReduction:never|always|controllable,
// imageWidth:range,
// imageHeight:range,
// fillLightMode:arr_of_auto|off|flash
media_tests.innerHTML +=
"--------------------------------" +
"photoCapabilities.redEyeReduction: " + photoCapabilities.redEyeReduction +
"photoCapabilities.imageWidth.min: " + photoCapabilities.imageWidth.min +
"photoCapabilities.imageWidth.max: " + photoCapabilities.imageWidth.max +
"photoCapabilities.imageWidth.step: " + photoCapabilities.imageWidth.step +
"photoCapabilities.imageHeight.min: " + photoCapabilities.imageHeight.min +
"photoCapabilities.imageHeight.max: " + photoCapabilities.imageWidth.max +
"photoCapabilities.imageHeight.step: " + photoCapabilities.imageHeight.step +
"photoCapabilities.fillLightMode: " + photoCapabilities.fillLightMode;
// // Map zoom to a slider element.
// input.min = capabilities.zoom.min;
// input.max = capabilities.zoom.max;
// input.step = capabilities.zoom.step;
// input.value = settings.zoom;
// input.oninput = function(event) {
// track.applyConstraints({advanced: [ {zoom: event.target.value} ]});
// }
}).catch(error => {
media_tests.innerHTML += (error.name || error)
});
// imageCapture.grabFrame().then(function(imageBitmap) {
// console.log('Grabbed frame:', imageBitmap);
// canvas.width = imageBitmap.width;
// canvas.height = imageBitmap.height;
// canvas.getContext('2d').drawImage(imageBitmap, 0, 0);
// canvas.classList.remove('hidden');
// }).catch(function(error) {
// console.log('grabFrame() error: ', error);
// });
// imageCapture.setOptions({
// zoom: zoomInput.value
// });
}
function AtrackCapSett() {
media_tests.innerHTML="";
trackCapSett(a_track);
}
function VtrackCapSett() {
media_tests.innerHTML="";
trackCapSett(v_track);
}
function trackCapSett(track) {
if(!track){media_tests.innerHTML="No track to check";return;}
media_tests.innerHTML+="---getSettings---";
let getSettings = track.getSettings();
for (let sett in getSettings) {
if (getSettings.hasOwnProperty(sett)) {
media_tests.innerHTML+=sett+": "+getSettings[sett];
}
}
media_tests.innerHTML+="---getCapabilities---";
let getCapabilities = track.getCapabilities();
for (let cap in getCapabilities) {
if (getCapabilities.hasOwnProperty(cap)) {
media_tests.innerHTML+=cap+": "+getSettings[cap];
}
}
// track.getSettings().facingMode
// JSON.stringify(v_track.getSettings(), null, 2);
// JSON.stringify(a_track.getSettings(), null, 2);
}
// pauseButton.onclick = function(evt) {
// var newState = !myAudioTrack.enabled;
// pauseButton.innerHTML = newState ? "▶️" : "⏸️";
// myAudioTrack.enabled = newState;
// }
// // Find the canvas element to capture
// var canvasElt = document.getElementsByTagName("canvas")[0];
// // Get the stream
// var stream = canvasElt.captureStream(25); // 25 FPS
// // Do things to the stream ...
// // Obtain the canvas associated with the stream
// var canvas = stream.canvas;
function getSupportedConstraints() {
media_tests.innerHTML="";
let supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
for (let constraint in supportedConstraints) {
if (supportedConstraints.hasOwnProperty(constraint)) {
media_tests.innerHTML+=constraint;
}
}
}
// let supported = navigator.mediaDevices.getSupportedConstraints();
// document.getElementById("frameRateSlider").disabled = !supported["frameRate"];
// CAPTURE
const capture_video = document.getElementById("capture_video");
var capture_options = {
video: {cursor:"never"},
audio: false
};
// Set event listeners for the start and stop buttons
document.getElementById("capture_start").addEventListener("click", function(evt) {
startCapture();
}, false);
document.getElementById("capture_stop").addEventListener("click", function(evt) {
stopCapture();
}, false);
async function startCapture() {
media_tests.innerHTML="";
try {
capture_video.srcObject = await navigator.mediaDevices.getDisplayMedia(capture_options);
dumpOptionsInfo();
} catch(err) {
media_tests.innerHTML="Error: "+err;
}
}
function stopCapture(evt) {
let tracks = capture_video.srcObject.getTracks();
tracks.forEach(track => track.stop());
capture_video.srcObject = null;
}
function dumpOptionsInfo() {
const videoTrack = capture_video.srcObject.getVideoTracks()[0];
media_tests.innerHTML+="Track settings:";
media_tests.innerHTML+=JSON.stringify(videoTrack.getSettings(), null, 2);
media_tests.innerHTML+="Track constraints:";
media_tests.innerHTML+=JSON.stringify(videoTrack.getConstraints(), null, 2);
}
// var animations_tests = document.getElementById("animations_tests");
// var easingFunctions = [
// 'frames(10)',
// 'steps(10)',
// 'ease-in'
// ]
// var keyFrames = [
// { width: '0%', background : 'red'},
// { width: '100%', background : 'blue'},
// ]
// var divs = document.querySelectorAll('.a_easing');
// for(var i = 0; i < divs.length; i++) {
// divs[i].animate(keyFrames, {
// easing : easingFunctions[i],
// duration : 5000,
// iterations: Infinity
// });
// }
var boxRotationTiming = {
duration: 1000,
iterations: 1,
fill: "none"
};
var boxRotationKeyframes = [
{ transform: "rotate(0deg)" },
{ transform: "rotate(360deg)" }
];
document.getElementById("animateButton").addEventListener("click", event => {
document.getElementById("box").animate(
boxRotationKeyframes,
boxRotationTiming
);
}, false);
// // slow down all animations on a page
// document.getAnimations().forEach(
// function (animation) {
// animation.playbackRate *= .5;
// }
// );
var pageTimeline = document.timeline;
var thisMoment = pageTimeline.currentTime;
// share a single documentTimeline among multiple animations
// manipulate just that group of animations via their shared timeline
// start all the cats animating 500 milliseconds into their animations
var cats = document.querySelectorAll('.sharedTimelineCat');
cats = Array.prototype.slice.call(cats);
var sharedTimeline = new DocumentTimeline({ originTime: 500 });
cats.forEach(function(cat) {
var catKeyframes = new KeyframeEffect(cat, keyframes, timing);
var catAnimation = new Animation(catKeyframes, sharedTimeline);
catAnimation.play();
});
animation.ready.then(function() {
// Do whatever needs to be done when
// the animation is ready to run
});
// keyframes formats
// 1 - array of objects (keyframes) consisting of properties and values to iterate
element.animate([
{ // from
opacity: 0,
color: "#fff"
},
{ // to
opacity: 1,
color: "#000"
}
// with offset
// { opacity: 1 },
// { opacity: 0.1, offset: 0.7 },
// { opacity: 0 }
// with easing
// { opacity: 1, easing: 'ease-out' },
// { opacity: 0.1, easing: 'ease-in' },
// { opacity: 0 }
], 2000);
// 2 - object containing key-value pairs consisting of the property to animate
// and an array of values to iterate over
element.animate({
opacity: [ 0, 1 ], // [ from, to ]
color: [ "#fff", "#000" ] // [ from, to ]
//
// opacity: [ 0, 1 ], // offset: 0, 1
// backgroundColor: [ "red", "yellow", "green" ], // offset: 0, 0.5, 1
//
// opacity: [ 0, 0.9, 1 ],
// offset: [ 0, 0.8 ], // Shorthand for [ 0, 0.8, 1 ]
// easing: [ 'ease-in', 'ease-out' ],
},2000);
// property names are specified using camel-case (backgroundColor,backgroundPositionX,margin)
// exceptions are: cssFloat, cssOffset
// special attributes: easing, offset, composite
// Loop through each tear
tears.forEach(function(el) {
// Animate each tear
el.animate(
tearsFalling,
{
delay: getRandomMsRange(-1000, 1000), // randomized for each tear
duration: getRandomMsRange(2000, 6000), // randomized for each tear
iterations: Infinity,
easing: "cubic-bezier(0.6, 0.04, 0.98, 0.335)"
});
});
var getRandomMsRange = function(min, max) {
return Math.random() * (max - min) + min;
}
document.getElementById("alice_arm").animate([
{ transform: 'rotate(10deg)' },
{ transform: 'rotate(-40deg)' }
], {
easing: 'steps(2, end)',
iterations: Infinity,
direction: 'alternate',
duration: 600
});
// Define the key frames
var spriteFrames = [
{ transform: 'translateY(0)' },
{ transform: 'translateY(-100%)' }
];
// Get the element that represents Alice and the Red Queen
var redQueen_alice_sprite = document.getElementById('red-queen_and_alice_sprite');
// Animate Alice and the Red Queen using steps()
var redQueen_alice = redQueen_alice_sprite.animate(
spriteFrames, {
easing: 'steps(7, end)',
direction: "reverse",
duration: 600,
playbackRate: 1,
iterations: Infinity
});
// waits until all animations running on the element elem have finished
// then deletes the element from the DOM tree
Promise.all(
elem.getAnimations().map(
function(animation){
return animation.finished;
}
)).then(
function() {
return elem.remove();
}
);
animation.oncancel = function() { animation.effect.target.remove(); };
// interfaceElement.addEventListener("mousedown", function() {
// try {
// player.finish();
// } catch(e if e instanceof InvalidState) {
// console.log("finish() called on paused or finished animation.");
// } catch(e) {
// logMyErrors(e); //pass exception object to error handler
// }
// });
speedSelector.addEventListener('input', evt => {
cartoon.updatePlaybackRate(parseFloat(evt.target.value));
cartoon.ready.then(() => {
console.log(`Playback rate set to ${cartoon.playbackRate}`);
});
});
// The cake has its own animation:
var nommingCake = document.getElementById('eat-me_sprite').animate(
[
{ transform: 'translateY(0)' },
{ transform: 'translateY(-80%)' }
], {
fill: 'forwards',
easing: 'steps(4, end)',
duration: aliceChange.effect.timing.duration / 2
});
nommingCake.pause(); // dont play immediately
// This function will play when ever a user clicks or taps
var growAlice = function() {
aliceChange.play();
nommingCake.play();
}
// var shrinkAlice = function() {
// aliceChange.playbackRate = -1;
// aliceChange.play();
// // // same as
// // aliceChange.reverse();
// drinking.play()
// }
// When a user holds their mouse down or taps, call growAlice to make all the animations play.
cake.addEventListener("mousedown", growAlice, false);
cake.addEventListener("touchstart", growAlice, false);
var stopPlayingAlice = function() {
aliceChange.pause();
nommingCake.pause();
drinking.pause();
};
var dnd_div1 = document.getElementById("dnd_div1");
var dnd_div2 = document.getElementById("dnd_div2");
var dnd_tests = document.getElementById("dnd_tests");
function allowDrop(ev) {
ev.preventDefault();
ev.target.style.border = '3px dotted blue';
}
function drag(ev) {
ev.dataTransfer.setData("text", ev.target.id);
// var dataList = ev.dataTransfer.items;
// dataList.add(ev.target.id, "text/plain");
// // Add some other items to the drag payload
// dataList.add("... paragraph ...", "text/html");
// dataList.add("http://www.example.org","text/uri-list");
}
function drop(ev, el) {
ev.preventDefault();
var data = ev.dataTransfer.getData("text");
el.appendChild(document.getElementById(data));
dnd_div1.style.border = '1px solid black';
dnd_div2.style.border = '1px solid black';
// var data = event.dataTransfer.items;
// for (var i = 0; i < data.length; i += 1) {
// if (
// (data[i].kind == 'string') &&
// (data[i].type.match('^text/plain'))
// ) {
// // This item is the target node
// data[i].getAsString(function (s){
// ev.target.appendChild(document.getElementById(s));
// });
// } else if (
// (data[i].kind == 'string') &&
// (data[i].type.match('^text/html'))
// ) {
// // Drag data item is HTML
// console.log("... Drop: HTML");
// } else if (
// (data[i].kind == 'file') &&
// (data[i].type.match('^image/'))
// ) {
// // Drag data item is an image file
// var f = data[i].getAsFile();
// console.log("... Drop: File ");
// }
// }
}
// function dragover_handler(ev) {
// console.log("dragOver");
// ev.preventDefault();
// // Set the dropEffect to move
// ev.dataTransfer.dropEffect = "move"
// }
// function dragend_handler(ev) {
// console.log("dragEnd");
// var dataList = ev.dataTransfer.items;
// for (var i = 0; i < dataList.length; i++) {
// dataList.remove(i);
// }
// dataList.clear(); // Clear any remaining drag data
// }
setInterval(function(){
dnd_tests.innerHTML = "";
var div1_childs_ids = "DIV 1:";
var div1_childs = dnd_div1.children;
for (var i=0, child; child=div1_childs[i]; i++) {
div1_childs_ids += child.id
}
var div2_childs_ids = "DIV 2:";
var div2_childs = dnd_div2.children;
for (var i=0, child; child=div2_childs[i]; i++) {
div2_childs_ids += child.id
}
dnd_tests.innerHTML = div1_childs_ids + div2_childs_ids;
}, 500);
// var el = document.getElementById('drag');
// el.addEventListener("touchstart", handleStart, false);
// el.addEventListener("touchend", handleEnd, false);
// el.addEventListener("touchcancel", handleCancel, false);
// el.addEventListener("touchleave", handleEnd, false);
// el.addEventListener("touchmove", handleMove, false);
// function handleStart(event) {
// // Handle the start of the touch
// }
var pl_tests = document.querySelector('#pl_tests');
var canvas = document.querySelector('#pl_tests_canvas');
var ctx = canvas.getContext('2d');
var x = 50;
var y = 50;
const RADIUS =5;
function pl_canvasDraw() {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#f00";
ctx.beginPath();
ctx.arc(x, y, RADIUS, 0, degToRad(360), true);
ctx.fill();
}
pl_canvasDraw();
// pointer lock object forking for cross browser
canvas.requestPointerLock = canvas.requestPointerLock ||
canvas.mozRequestPointerLock;
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock;
canvas.onclick = function() {
canvas.requestPointerLock();
};
document.addEventListener('pointerlockchange', lockChangeAlert, false);
document.addEventListener('mozpointerlockchange', lockChangeAlert, false);
function lockChangeAlert() {
if (
document.pointerLockElement === canvas ||
document.mozPointerLockElement === canvas
) {
pl_logger("pointer lock status is now locked");
document.addEventListener("mousemove", updatePosition, false);
} else {
pl_logger("pointer lock status is now unlocked");
document.removeEventListener("mousemove", updatePosition, false);
}
}
document.addEventListener('pointerlockerror', lockError, false);
document.addEventListener('mozpointerlockerror', lockError, false);
function lockError(e) {
pl_logger("pointer lock failed");
}
var animation;
function updatePosition(e) {
x += e.movementX;
y += e.movementY;
if (x > canvas.width + RADIUS) {
x = -RADIUS;
}
if (y > canvas.height + RADIUS) {
y = -RADIUS;
}
if (x < -RADIUS) {
x = canvas.width + RADIUS;
}
if (y < -RADIUS) {
y = canvas.height + RADIUS;
}
pl_logger("X position: "+x+", Y position: "+y);
if (!animation) {
animation = requestAnimationFrame(function() {
animation = null;
pl_canvasDraw();
});
}
}
function degToRad(degrees) {
var result = Math.PI / 180 * degrees;
return result;
}
function pl_logger(txt) {
pl_tests.innerHTML = txt + pl_tests.innerHTML;
}
// el.addEventListener("touchstart", handleStart, false);
// el.addEventListener("touchend", handleEnd, false);
// el.addEventListener("touchcancel", handleCancel, false);
// el.addEventListener("touchleave", handleEnd, false);
// el.addEventListener("touchmove", handleMove, false);
<?php
// php/stream.php file
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$time = date('r');
echo "data: The server time is: {$time}\n\n";
flush();
?>
var sse_tests = document.getElementById("sse_tests");
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("php/stream.php");
// source.onopen = function(event) {
// sse_tests.innerHTML += "Connection to the stream is opened";
// };
source.onmessage = function(event) {
sse_tests.innerHTML += event.data;
};
// source.onerror = function(event) {
// sse_tests.innerHTML += "Stream errors";
// };
} else {
sse_tests.innerHTML = "server-sent events unsupported...";
}
var pc = new RTCPeerConnection();
var state = pc.iceGatheringState;
var state = pc.iceConnectionState;
var connectionState = pc.connectionState;
pc.ontrack = function(event) {
document.getElementById("received_video").srcObject = event.streams[0];
document.getElementById("hangup-button").disabled = false;
};
pc.onsignalingstatechange = function(event) {
if (pc.signalingState === "have-local-pranswer") {
// setLocalDescription() has been called with an answer
}
};
pc.onremovestream = function(ev) { alert("onremovestream event detected!"); };
pc.onpeeridentity = function(ev) { alert("onpeeridentity event detected!"); };
pc.onidpvalidationerror = function(ev) { alert("onidpvalidationerror event detected!"); };
pc.onidpassertionerror = function(ev) { alert("onidpassertionerror event detected!"); };
pc.onidentityresult = function(ev) { alert("onidentityresult event detected!"); };
pc.onnegotiationneeded = function() {
pc.createOffer().then(function(offer) {
return pc.setLocalDescription(offer);
})
.then(function() {
// Send the offer to the remote peer through the signaling server
});
})
.catch(reportError);
}
pc.onicegatheringstatechange = function() {
let label = "Unknown";
switch(pc.iceGatheringState) {
case "new":
case "complete":
label = "Idle";
break;
case "gathering":
label = "Determining route";
break;
}
document.getElementById("iceStatus").innerHTML = label;
}
pc.oniceconnectionstatechange = function(event) {
if (pc.iceConnectionState === "failed" ||
pc.iceConnectionState === "disconnected" ||
pc.iceConnectionState === "closed") {
// Handle the failure
}
};
pc.onicecandidate = function(event) {
if (event.candidate) {
// Send the candidate to the remote peer
} else {
// All ICE candidates have been sent
}
}
pc.ondatachannel = function(ev) {
console.log('Data channel is created!');
ev.channel.onopen = function() {
console.log('Data channel is open and ready to be used.');
};
};
pc.onconnectionstatechange = function(event) {
switch(pc.connectionState) {
case "connected":
// The connection has become fully connected
break;
case "disconnected":
case "failed":
// One or more transports has terminated unexpectedly or in an error
break;
case "closed":
// The connection has been closed
break;
}
}
var iceServers = pc.defaultIceServers;
if (iceServers.length === 0) {
// Deal with the lack of default ICE servers, possibly by using our own defaults
}
var channel = pc.createDataChannel("Mydata");
channel.onopen = function(event) {
channel.send('sending a message');
}
channel.onmessage = function(event) {
console.log(event.data);
}
// Determine the largest message size that can be sent
var sctp = pc.sctp;
var maxMessageSize = sctp.maxMessageSize;
var sd = pc.remoteDescription;
if (sd) {
alert("Remote session: type='" +
sd.type + "'; sdp description='" +
sd.sdp + "'");
}
else {
alert("No remote session yet.");
}
var sd = pc.currentRemoteDescription;
if (sd) {
alert("Local session: type='" +
sd.type + "'; sdp description='" +
sd.sdp + "'");
}
else {
alert("No local session yet.");
}
var ld = pc.localDescription;
if (ld) {
alert("Local session: type='" +
ld.type + "'; sdp description='" +
ld.sdp + "'");
}
else {
alert("No local session yet.");
}
var sd = pc.currentLocalDescription;
if (sd) {
alert("Local session: type='" +
sd.type + "'; sdp description='" +
sd.sdp + "'");
}
else {
alert("No local session yet.");
}
var identity = pc.peerIdentity;
if (identity) {
alert("Identity of the peer: idp='" +
identity.idp + "'; assertion='" +
identity.name + "'");
}
else {
alert("Identity of the peer has not been verified");
}
// The following code might be used to handle an offer from a peer when
// it isn't known whether it supports trickle ICE.
pc.setRemoteDescription(remoteOffer)
.then(_ => pc.createAnswer())
.then(answer => pc.setLocalDescription(answer))
.then(_ =>
if (pc.canTrickleIceCandidates) {
return pc.localDescription;
}
return new Promise(r => {
pc.addEventListener('icegatheringstatechange', e => {
if (e.target.iceGatheringState === 'complete') {
r(pc.localDescription);
}
});
});
})
.then(answer => sendAnswerToPeer(answer)) // signaling message
.catch(e => handleError(e));
pc.addEventListener('icecandidate', e => {
if (pc.canTrickleIceCandidates) {
sendCandidateToPeer(e.candidate); // signaling message
}
});
var crypto_tests = document.getElementById("crypto_tests");
function getRandomNumbers() {
var array = new Uint32Array(10);
window.crypto.getRandomValues(array);
crypto_tests.innerHTML = "random numbers:"
for (var i = 0; i < array.length; i++) {
crypto_tests.innerHTML += array[i];
}
}
let text = 'An obscure body in the S-K System, your majesty. The inhabitants refer to it as the planet Earth.';
async function digestMessage(message) {
let encoder = new TextEncoder();
let data = encoder.encode(message);
let digest = await window.crypto.subtle.digest('SHA-256', data);
console.log(digest.byteLength); // 32 for SHA-256
}
digestMessage(text);
// RSA-OAEP ENCRYPT
function getMessageEncoding() {
const messageBox = document.querySelector(".rsa-oaep #message");
let message = messageBox.value;
let enc = new TextEncoder();
return enc.encode(message);
}
function encryptMessage(publicKey) {
let encoded = getMessageEncoding();
return window.crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
publicKey,
encoded
);
}
// RSA-OAEP DECRYPT
function decryptMessage(privateKey, ciphertext) {
return window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
privateKey,
ciphertext
);
}
// RSA-PSS - SIGN AND VERIFY
// Fetch the contents of the "message" textbox, and encode it
// in a form we can use for sign operation
function getMessageEncoding() {
const messageBox = document.querySelector(".rsa-pss #message");
let message = messageBox.value;
let enc = new TextEncoder();
return enc.encode(message);
}
let encoded = getMessageEncoding();
let signature = await window.crypto.subtle.sign(
{
name: "RSA-PSS",
saltLength: 32,
},
privateKey,
encoded
);
// Fetch the contents of the "message" textbox, and encode it
// in a form we can use for sign operation
function getMessageEncoding() {
const messageBox = document.querySelector(".rsa-pss #message");
let message = messageBox.value;
let enc = new TextEncoder();
return enc.encode(message);
}
// Fetch the encoded message-to-sign and verify it against the stored signature
// If it checks out, set the "valid" class on the signature
// Otherwise set the "invalid" class
async function verifyMessage(publicKey) {
const signatureValue = document.querySelector(".rsa-pss .signature-value");
signatureValue.classList.remove("valid", "invalid");
let encoded = getMessageEncoding();
let result = await window.crypto.subtle.verify(
{
name: "RSA-PSS",
saltLength: 32,
},
publicKey,
signature,
encoded
);
signatureValue.classList.add(result ? "valid" : "invalid");
}
// KEY PAIR GENERATION
// RSA-OAEP encryption key pair generation
let keyPair = window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["encrypt", "decrypt"]
);
// Elliptic curve key pair generation
let keyPair = window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-384"
},
true,
["sign", "verify"]
);
// HMAC signing key generation
let key = window.crypto.subtle.generateKey(
{
name: "HMAC",
hash: {name: "SHA-512"}
},
true,
["sign", "verify"]
);
// AES-GCM encryption key generation
let key = window.crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"]
);
var performance_tests = document.getElementById("performance_tests");
var performance_obj = null;
var m1, m2, measure_marks; // for now() cases
// performance.timeOrigin
// performance.clearResourceTimings()
// performance.setResourceTimingBufferSize(max_perf_entries)
if ('serviceWorker' in navigator) {
var performance_obj = window.performance;
} else {
performance_tests.innerHTML="PerformanceAPI not supported"
}
function create_mark(name) {
performance.mark(name);
}
function mark_clear(name) {
performance.clearMarks(name);
}
function create_measure(name) {
performance.measure(
name,
'm1',
'm2'
);
}
function show_measure(name) {
performance_tests.innerHTML="";
var measures = performance.getEntriesByName('measure_marks');
var measure = measures[0];
if(!measure){performance_tests.innerHTML="not found...";return;}
performance_tests.innerHTML =
"measure.duration: " + measure.duration + "ms" +
"measure.entryType: " + measure.entryType +
"measure.name: " + measure.name +
"measure.startTime: " + measure.startTime;
}
function measure_clear(name) {
if (name === undefined) {
performance.clearMeasures(); return;
}
performance.clearMeasures(name);
}
function show_performance_json() {
performance_tests.innerHTML="";
var pj = performance.toJSON();
utils_show_obj(pj, performance_tests);
performance_tests.innerHTML+=JSON.stringify(pj)+" ]";
}
function show_entry(obj) {
var properties = ["name", "entryType", "startTime", "duration"];
// var methods = ["toJSON"];
for (var i=0; i < properties.length; i++) {
// check each property
var supported = properties[i] in obj;
if (supported)
performance_tests.innerHTML+=properties[i] +
" = " + obj[properties[i]];
else
performance_tests.innerHTML+=properties[i] +
" = Not supported";
}
// for (var i=0; i < methods.length; i++) {
// // check each method
// var supported = typeof obj[methods[i]] == "function";
// if (supported) {
// var js = obj[methods[i]]();
// performance_tests.innerHTML+=methods[i] + "() = " +
// JSON.stringify(js);
// } else {
// performance_tests.innerHTML+=methods[i] + " = Not supported";
// }
// }
}
function run_tests() {
mark_clear();
measure_clear();
performance_tests.innerHTML = "tests ...";
var t0 = performance.now();
do_work(7654321+utils_randomInt());
var t1 = performance.now();
performance_tests.innerHTML+="measurement with now(): "+(t1-t0)+"ms";
performance.mark("pm1_start");
do_work(54321+utils_randomInt());
performance.mark("pm1_end");
performance.mark("pm2_start");
do_work(654321+utils_randomInt());
performance.mark("pm2_end");
performance.mark("pm3_start");
do_work(7654321+utils_randomInt());
performance.mark("pm3_end");
// // specific entries
// p = performance.getEntries({name : "Begin", entryType: "mark"});
// for (var i=0; i < p.length; i++) {
// performance_tests.innerHTML += "Begin[" + i + "]";
// }
// "mark" entries
performance_tests.innerHTML += "mark only entries:"
var p = performance.getEntriesByType("mark");
for (var i=0; i < p.length; i++) {
performance_tests.innerHTML+=p[i].name+" [ start:"+p[i].startTime+
" duration:"+p[i].duration+" ]";
}
// // "mark" entries named "Begin"
// p = performance.getEntriesByName("Begin", "mark");
// for (var i=0; i < p.length; i++) {
// performance_tests.innerHTML += "Mark and Begin entry[" + i + "]: name = "
// + p[i].name +
// "startTime = " + p[i].startTime +
// "duration = " + p[i].duration;
// }
// each entry
performance_tests.innerHTML += "all entries:"
var p = performance.getEntries();
for (var i=0; i < p.length; i++) {
performance_tests.innerHTML+=p[i].name+" [ start:"+p[i].startTime+
" duration:"+p[i].duration+" ]";
}
}
function do_work(cicles) {
for (let index = 0; index < cicles; index++) {
var tests = (index+index)+cicles+(utils_randomInt()*cicles+cicles);
tests*tests;
}
}
function show_navigation() {
performance_tests.innerHTML=""
// Use getEntriesByType() to just get the "navigation" events
var perfEntries = performance.getEntriesByType("navigation");
for (var i=0; i < perfEntries.length; i++) {
var p = perfEntries[i];
performance_tests.innerHTML+="navigation entry [" + i + "]";
// dom Properties
performance_tests.innerHTML+="DOM content loaded = " +
(p.domContentLoadedEventEnd - p.domContentLoadedEventStart);
performance_tests.innerHTML+="DOM complete = " +
p.domComplete;
performance_tests.innerHTML+="DOM interactive = " +
p.interactive;
// document load and unload time
performance_tests.innerHTML+="document load = " +
(p.loadEventEnd - p.loadEventStart);
performance_tests.innerHTML+="document unload = " +
(p.unloadEventEnd - p.unloadEventStart);
// other properties
performance_tests.innerHTML+="type = " +
p.type;
performance_tests.innerHTML+="redirectCount = " +
p.redirectCount;
}
}
// interupt current flow !!!
// var observer = new PerformanceObserver((list, obj) => {
// performance_tests.innerHTML="marks created";
// var entries = list.getEntries();
// for (var i=0; i < entries.length; i++) {
// show_entry(entries[i],performance_tests);
// }
// // observer.disconnect(); // interupt observing
// });
// observer.observe({entryTypes: ["mark"]});
// // var records = observer.takeRecords();
// // console.log(records[0].name);
// // console.log(records[0].startTime);
// // console.log(records[0].duration);
// var observer2 = new PerformanceObserver((list, observer) => {
// performance_tests.innerHTML="measure created";
// var entries = list.getEntries();
// for (var i=0; i < entries.length; i++) {
// show_entry(entries[i],performance_tests);
// }
// });
// observer2.observe({entryTypes: ["measure"]});
function show_resources() {
performance_tests.innerHTML="";
var p = performance.getEntriesByType("resource");
for (var i=0; i < p.length; i++) {
show_entry(p[i]);
resource_props(p[i]);
}
}
function resource_props(perfEntry) {
// Print timestamps of the *start and *end properties
properties = [
"requestStart",
"domainLookupStart",
"domainLookupEnd",
"connectStart",
"responseStart",
"responseEnd",
"fetchStart",
"connectEnd",
"transferSize",
"encodedBodySize",
"decodedBodySize",
"redirectStart",
"redirectEnd",
"secureConnectionStart",
"serverTiming",
"workerStart",
"nextHopProtocol",
"initiatorType"
];
for (var i=0; i < properties.length; i++) {
// check each property
var supported = properties[i] in perfEntry;
if (supported) {
var value = perfEntry[properties[i]];
performance_tests.innerHTML+=properties[i] + " = " + value;
} else {
performance_tests.innerHTML+=properties[i] + " = NOT supported";
}
}
}