Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

267
gaia/dev_apps/upnp-test/js/app.js Executable file
View file

@ -0,0 +1,267 @@
(function(exports) {
var DEBUG = false;
'use strict';
var savedServices = {};
var debugEl;
var audioPlayer;
var videoPlayer;
var imagePlayer;
var discoverButton;
var currentPlayer;
var playerToggler;
var folderList;
function debugLog(msg, level) {
if (!DEBUG && level === 'debug') {
return;
}
var logEl = document.createElement('div');
logEl.textContent = msg;
debugEl.appendChild(logEl);
}
function toggleFolder(evt) {
evt.preventDefault();
var elem = evt.target;
if (elem.nextSibling && elem.nextSibling.tagName == 'DIV') {
// folder is opened. Close it.
elem.parentElement.removeChild(elem.nextSibling);
elem.classList.remove('opened');
} else {
// folder is closed. Open it.
debugLog(elem.serviceId, 'debug');
elem.classList.add('opened');
browseFolder(
elem.dataset.serviceId, elem.hash.substr(1), evt.target);
}
}
function togglePlayer(value) {
if (typeof value != 'boolean') {
value = currentPlayer.classList.contains('hide');
}
audioPlayer.classList.add('hide');
videoPlayer.classList.add('hide');
imagePlayer.classList.add('hide');
if (value) {
currentPlayer.classList.remove('hide');
}
}
function switchPlayer(type) {
audioPlayer.pause();
videoPlayer.pause();
if (type == 'audio') {
currentPlayer = audioPlayer;
} else if (type == 'video') {
currentPlayer = videoPlayer;
} else if (type == 'image') {
currentPlayer = imagePlayer;
}
togglePlayer(true);
}
function playFile(evt) {
evt.preventDefault();
var fileType = evt.target.dataset.mime.split('/')[0].toLowerCase();
var mimeType = evt.target.dataset.mime.split('/')[1].toLowerCase();
if (evt.target.href.substr(-3) == 'ogv') {
mimeType = 'video/ogg';
} else if (evt.target.href.substr(-3) == 'ogg') {
mimeType = 'audio/ogg';
}
switchPlayer(fileType);
currentPlayer.hidden = false;
currentPlayer.src = evt.target.href;
currentPlayer.setAttribute('type', mimeType);
currentPlayer.play();
}
function browseFolder(serviceId, folderId, folderElement) {
var mediaServer = savedServices[serviceId];
if (!mediaServer) {
return;
}
folderId = folderId || '';
mediaServer.browse(folderId).then(function(response) {
if (!response || !response.data) {
return;
}
var data = response.data.Result.replace(/&/g, "&");
debugLog(data);
if (data.indexOf('xmlns:dlna') == -1) {
data = data.replace('<DIDL-Lite ',
'<DIDL-Lite xmlns:dlna="urn:schemas-dlna-org:device-1-0" ');
}
var parser = new DOMParser();
var xmlResponse = parser.parseFromString(data, 'application/xml');
var lists = xmlResponse.documentElement.children;
var sublist = document.createElement('div');
sublist.className = 'sublist';
for (var i = 0; i < lists.length; i++) {
var item = lists[i];
var titleElem = item.getElementsByTagName('title')[0] ||
item.getElementsByTagName('dc:title')[0];
var title;
if (titleElem) {
title = titleElem.textContent;
}
var newElem;
if (item.tagName == 'container') {
newElem = document.createElement('a');
newElem.dataset.serviceId = serviceId;
newElem.addEventListener('click', toggleFolder);
newElem.href = '#' + item.getAttribute('id');
newElem.textContent = title;
newElem.className = 'folder';
sublist.appendChild(newElem);
} else if (item.tagName == 'item') {
var linkElem = item.getElementsByTagName('res')[0];
var link, mime;
if (linkElem) {
link = linkElem.textContent;
mime = linkElem.getAttribute('protocolInfo').split(':')[2];
}
newElem = document.createElement('a');
newElem.addEventListener('click', playFile);
newElem.dataset.mime = mime;
newElem.href = link;
newElem.textContent = title;
newElem.className = mime.split('/')[0];
sublist.appendChild(newElem);
}
}
if (!folderElement) {
folderList.appendChild(sublist);
} else {
folderElement.parentElement.
insertBefore(sublist, folderElement.nextSibling);
}
});
}
function processService(service, i) {
service._index = i;
if (savedServices[service.id]) {
return;
}
var mediaServer =
new Plug.UPnP_ContentDirectory(service, { debug: false });
if (!savedServices[service.id]) {
savedServices[service.id] = mediaServer;
}
mediaServer.getSystemUpdateId().then(function(response) {
if (response && response.data) {
debugLog('Service[' + service._index + '] is reporting UpdateId = ' +
response.data.Id );
} else {
debugLog('Service[' + service._index + '] is reporting no response');
}
}).then(null, function(error) { // Handle any errors
debugLog('An error occurred: ' + error.description);
});
var serverElem = document.createElement('div');
serverElem.className = 'server';
var parser = new DOMParser();
var configDocument = parser.parseFromString(mediaServer.svc.config, 'text/xml');
serverElem.textContent = configDocument.getElementsByTagName('friendlyName')[0].textContent;
folderList.appendChild(serverElem);
browseFolder(service.id, null, serverElem);
}
function onServices(services) {
var idx = services.length;
services.addEventListener('servicefound', function _servicefound(e) {
debugLog(services.length + ' UPnP service' +
(services.length !== 1 ? 's' : '') +
' found in the network');
processService(services[idx], idx);
idx++;
});
debugLog(services.length + ' UPnP service' +
(services.length !== 1 ? 's' : '') +
' found in the network');
if (services.length === 0) {
return;
}
for (var i = 0; i < services.length; i++) {
var service = services[i];
processService(service, i);
}
}
function discover() {
if (navigator.getNetworkServices) {
debugLog('Search for UPnP services in the network');
navigator.getNetworkServices('upnp:urn:schemas-upnp-org:service:ContentDirectory:1')
.then(onServices, function(e) {
debugLog('An error occurred obtaining UPnP Services [CODE: ' +
error.code + ']');
});
} else {
debugLog(
'navigator.getNetworkServices API is not supported in this browser');
}
}
function clearLog() {
debugEl.innerHTML = '';
}
function init() {
folderList = document.getElementById('folderList');
debugEl = document.getElementById('debug');
audioPlayer = document.getElementById('audioPlayer');
videoPlayer = document.getElementById('videoPlayer');
imagePlayer = document.getElementById('imagePlayer');
playerToggler = document.getElementById('playerToggler');
discoverButton = document.getElementById('discoverButton');
clearLogButton = document.getElementById('clearLog');
currentPlayer = imagePlayer;
togglePlayer(false);
playerToggler.addEventListener('click', togglePlayer);
discoverButton.addEventListener('click', discover);
clearLogButton.addEventListener('click', clearLog);
discover();
}
window.addEventListener('load', function() {
init();
});
exports.ServiceManager = {
savedServices: savedServices
};
})(window);

View file

@ -0,0 +1,222 @@
/* Copyright (c) 2012 Opera Software ASA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A UPnP Media Server API for the following service types:
*
* - urn:schemas-upnp-org:service:ContentDirectory:1
* - urn:schemas-upnp-org:service:ContentDirectory:2
* - urn:schemas-upnp-org:service:ContentDirectory:3
* - urn:schemas-upnp-org:service:ContentDirectory:4
*
*/
!(function( global, undefined ) {
if( !global.Plug || !global.Plug.UPnP ) {
console.error("plug.play.js must be loaded before you can use this library");
return;
}
var allowedUPnPTypes = {
'urn:schemas-upnp-org:service:ContentDirectory:1': true,
'urn:schemas-upnp-org:service:ContentDirectory:2': true,
'urn:schemas-upnp-org:service:ContentDirectory:3': true,
'urn:schemas-upnp-org:service:ContentDirectory:4': true
};
var UPnPContentDirectory = function( serviceObj, opts ) {
Plug.UPnP.apply( this, [ serviceObj, opts ] );
this.upnpType = serviceObj.type;
if( serviceObj.type.indexOf('upnp:') === 0 ) {
this.upnpType = this.upnpType.replace('upnp:', '');
}
if( !allowedUPnPTypes[ this.upnpType ] ) {
console.error("Provided service is not a UPnP Media Server Service");
return;
}
// *** ContentDirectory:1 methods
this.getSystemUpdateId = function( callback ) {
return this.action('GetSystemUpdateID', {
request: {},
response: {
Id: {
type: this.types.ui4
}
}
}, callback);
};
this.getSearchCapabilities = function( callback ) {
return this.action('GetSearchCapabilities', {
request: {},
response: {
SearchCaps: {
type: this.types.string
}
}
}, callback);
};
this.getSortCapabilities = function( callback ) {
return this.action('GetSortCapabilities', {
request: {},
response: {
SortCaps: {
type: this.types.string
}
}
}, callback);
};
this.browse = function( objectId, browseFlag, filter, startingIndex, requestedCount, sortCriteria, callback ) {
return this.action('Browse', {
request: {
ObjectID: {
value: objectId || '0',
type: this.types.string
},
BrowseFlag: {
value: browseFlag || 'BrowseDirectChildren',
type: this.types.string
},
Filter: {
value: filter || '*',
type: this.types.string
},
StartingIndex: {
value: startingIndex || '0',
type: this.types.ui4
},
RequestedCount: {
value: requestedCount || '100000',
type: this.types.ui4
},
SortCriteria: {
value: sortCriteria,
type: this.types.string
}
},
response: {
Result: {
type: this.types.string
},
NumberReturned: {
type: this.types.ui4
},
TotalMatches: {
type: this.types.ui4
},
UpdateID: {
type: this.types.ui4
}
}
}, callback);
};
this.search = function( objectId, browseFlag, filter, startingIndex, requestedCount, sortCriteria, callback ) {
return this.action('Search', {
request: {
ObjectID: {
value: objectId || '0',
type: this.types.string
},
BrowseFlag: {
value: browseFlag || 'BrowseDirectChildren',
type: this.types.string
},
Filter: {
value: filter || '*',
type: this.types.string
},
StartingIndex: {
value: startingIndex || '0',
type: this.types.ui4
},
RequestedCount: {
value: requestedCount || '100000',
type: this.types.ui4
},
SortCriteria: {
value: sortCriteria,
type: this.types.string
}
},
response: {
Result: {
type: this.types.string
},
NumberReturned: {
type: this.types.ui4
},
TotalMatches: {
type: this.types.ui4
},
UpdateID: {
type: this.types.ui4
}
}
}, callback);
};
// *** ContentDirectory:2 methods
if(this.upnpType === 'urn:schemas-upnp-org:service:ContentDirectory:2') {
// TODO: GetFeatureList, GetSortExtensionCapabilities, MoveObject
}
// *** ContentDirectory:3 methods
if(this.upnpType === 'urn:schemas-upnp-org:service:ContentDirectory:3') {
// TODO
}
// *** ContentDirectory:4 methods
if(this.upnpType === 'urn:schemas-upnp-org:service:ContentDirectory:4') {
// TODO
}
};
UPnPContentDirectory.prototype = Object.create( Plug.UPnP.prototype );
// *** GLOBAL ASSIGNMENT ***
global.Plug.UPnP_ContentDirectory = UPnPContentDirectory;
})( this );

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,521 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* jshint ignore:start */
(function(global) {
// Spec information:
// http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
const SSDP_PORT = 1900;
const SSDP_ADDRESS = "239.255.255.250";
const SSDP_DISCOVER_MX = 2;
const SSDP_DISCOVER_PACKET =
"M-SEARCH * HTTP/1.1\r\n" +
"HOST: " + SSDP_ADDRESS + ":" + SSDP_PORT + "\r\n" +
"MAN: \"ssdp:discover\"\r\n" +
"MX: " + SSDP_DISCOVER_MX + "\r\n" +
"ST: %SEARCH_TARGET%\r\n\r\n";
const SSDP_RESPONSE_HEADER = /HTTP\/\d{1}\.\d{1} \d+ .*/;
const SSDP_HEADER = /^([^:]+):\s*(.*)$/;
var SimpleServiceDiscovery = {
_targets: [],
_commands: {},
_searching: false,
search: function _search(aInterval) {
aInterval = aInterval || 0;
if (aInterval > 0) {
this._searchRepeat = setInterval(this._search.bind(this), aInterval);
}
this._search();
},
stopSearch: function _stopSearch() {
if (this._searchRepeat) {
clearInterval(this._searchRepeat);
}
},
registerTarget: function _registerTarget(target) {
if (this._targets.indexOf(target) < 0) {
this._targets.push(target);
}
},
// internal function
_search: function _search() {
// create socket if not exist
if (!this._searchSocket) {
this._searchSocket = new UDPSocket({loopback: true, localPort: SSDP_PORT});
this._searchSocket.joinMulticastGroup(SSDP_ADDRESS);
this._searchSocket.onmessage = this._onmessage.bind(this);
window.addEventListener('beforeunload', function(evt) {
this._searchSocket.close();
}.bind(this));
}
this._searchSocket.opened.then((function() {
// Perform a UDP broadcast to search for SSDP devices
this._searchTimeout = setTimeout(this._searchShutdown.bind(this), SSDP_DISCOVER_MX * 1000);
this._searching = true;
var data = SSDP_DISCOVER_PACKET;
this._targets.forEach((function(target) {
var msgData = data.replace("%SEARCH_TARGET%", target);
var ok = this._searchSocket.send(msgData, SSDP_ADDRESS, SSDP_PORT);
}).bind(this));
}).bind(this)).catch(function(evt) {
});
},
_searchShutdown: function _searchShutdown() {
this._searching = false;
},
_onmessage: function _onmessage(e) {
// Listen for responses from specific targets. There could be more than
// one available.
var msg = String.fromCharCode.apply(null, new Uint8Array(e.data));
var lines = msg.toString().split("\r\n");
var firstLine = lines.shift();
var method = SSDP_RESPONSE_HEADER.test(firstLine) ? 'RESPONSE'
: firstLine.split(' ')[0].toUpperCase();
var headers = {};
lines.forEach(function (line) {
if (line.length) {
var pairs = line.match(/^([^:]+):\s*(.*)$/);
if (pairs) {
headers[pairs[1].toLowerCase()] = pairs[2];
}
}
});
if (this._commands[method]) {
this._commands[method].apply(this, [headers]);
}
},
_found: function _found(aService) {
// Use the REST api to request more information about this service
var xhr = new XMLHttpRequest({mozSystem: true});
xhr.open("GET", aService.location, true);
xhr.overrideMimeType("text/xml");
xhr.addEventListener("load", (function() {
if (xhr.status == 200) {
// walk through root device and all the embedded devices
var devices = xhr.responseXML.querySelectorAll('device');
for (var i = 0; i < devices.length; i++) {
this._parseDescriptor(devices[i], aService.location);
}
}
}).bind(this), false);
xhr.send(null);
},
_parseDescriptor: function _parseDescriptor(device, refUrl) {
var udn = device.querySelector("UDN").innerHTML;
var serviceList = device.querySelector('serviceList').querySelectorAll('service');
for (var i = 0; i < serviceList.length; i++) {
var service = serviceList[i];
var serviceType = service.querySelector('serviceType').innerHTML;
var eventsUrl = this._getAbsoluteURL(service.querySelector('eventSubURL'), refUrl);
var options = {};
options.id = udn + '::' + serviceType;
options.deviceId = udn;
options.name = service.querySelector('serviceId').innerHTML;
options.type = 'upnp:' + serviceType;
options.url = this._getAbsoluteURL(service.querySelector('controlURL').innerHTML, refUrl);
options.config = device.outerHTML;
if (eventsUrl) {
options.eventsUrl = eventsUrl.innerHTML;
}
options.expiryTimestamp = ''; //TODO
serviceHelper.add(new SSDPServiceRecord(options));
}
},
_getAbsoluteURL: function _getAbsoluteURL(url, refUrl) {
if (/^https?:\/\//.test(url)) {
return url;
} else {
var absURL = new URL(url, refUrl);
return absURL.toString();
}
}
};
SimpleServiceDiscovery._commands['RESPONSE'] = function _response(headers) {
if (this._searching && headers.location && this._targets.indexOf(headers.st) >= 0) {
this._found(headers);
}
};
SimpleServiceDiscovery._commands['NOTIFY'] = function _notify(headers) {
switch(headers.nts) {
case 'ssdp:alive':
var old_searching = this._searching;
this._searching = true;
headers['st'] = headers.nt;
this._commands['RESPONSE'].apply(this, [headers]);
this._searching = old_searching;
break;
case 'ssdp:byebye':
serviceHelper.remove(new SSDPServiceRecord({id: headers.usn}));
break;
}
};
SimpleServiceDiscovery._commands['M-SEARCH'] = function _msearch(headers) {
};
function SSDPServiceRecord(options) {
this.update(options);
}
SSDPServiceRecord.prototype = {
id: '',
deviceId: '',
name: '',
type: '',
url: '',
eventsUrl: '',
config: '',
expiryTimestamp: '',
update: function(options) {
var self = this;
['id', 'deviceId', 'name', 'type', 'url', 'config', 'expiryTimestamp'].forEach(function(option) {
self[option] = options[option];
});
if (options.eventsUrl) {
this.eventsUrl = options.eventsUrl;
}
}
};
// polyfill Network Service Discovery API
function NetworkService(record) {
this._record = record;
//XXX need weak reference
this._callbacks = { 'available': [], 'unavailable': [], 'notify': [] };
}
NetworkService.prototype = {
online: true,
get id() { return this._record.id; },
get name() { return this._record.name; },
get type() { return this._record.type; },
get url() { return this._record.url; },
get config() { return this._record.config; },
set onavailable(callback) {
this.addEventListener('available', callback);
},
set onunavailable(callback) {
this.addEventListener('unavailable', callback);
},
set onnotify(callback) {
this.addEventListener('notify', callback);
},
// EventTarget
addEventListener: function(type, callback, capture) {
if (!this._callbacks.hasOwnProperty(type)) {
return;
}
this._callbacks[type].push(callback);
},
removeEventListener: function(type, callback, capture) {
if (!this._callbacks.hasOwnProperty(type)) {
return;
}
var callbacks = this._callbacks[type];
var index = callbacks.indexOf(callback);
if (index >= 0) {
callbacks.splice(index, 1);
}
},
dispatchEvent: function(event) {
if (this._callbacks.hasOwnProperty(event.type)) {
var callbacks = this._callbacks[event.type];
if (!event.detail || !event.detail.target) {
event.detail = { target: this };
}
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](event);
}
}
return event.cancelable === false || event.defaultPrevented === false;
},
};
function NetworkServices(types, services) {
this._services = services || [];
this._types = types || [];
for (var i = 0; i < services.length; i++) {
this[i] = services[i];
}
//XXX need weak reference
this._callbacks = { 'servicefound': [], 'servicelost': [] };
}
NetworkServices.prototype = {
_append: function(service) {
var i = this._services.length;
this[i] = service;
this._services.push(service);
},
servicesAvailable: 0,
get length() {
return this._services.length;
},
getServiceById: function(id) {
for (var i = 0; i < this._services.length; i++) {
if (this._services[i].id === id) {
return this._services[i];
}
}
return null;
},
set onservicefound(callback) {
this.addEventListener('servicefound', callback);
},
set onservicelost(callback) {
this.addEventListener('servicelost', callback);
},
// EventTarget
addEventListener: function(type, callback) {
if (!this._callbacks.hasOwnProperty(type)) {
return;
}
this._callbacks[type].push(callback);
},
removeEventListener: function(type, callback, capture) {
if (!this._callbacks.hasOwnProperty(type)) {
return;
}
var callbacks = this._callbacks[type];
var index = callbacks.indexOf(callback);
if (index >= 0) {
callbacks.splice(index, 1);
}
},
dispatchEvent: function(event) {
if (this._callbacks.hasOwnProperty(event.type)) {
var callbacks = this._callbacks[event.type];
if (!event.detail || !event.detail.target) {
event.detail = {target: this};
}
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](event);
}
}
return event.cancelable === false || event.defaultPrevented === false;
},
};
function _hasRequestedType(service, types) {
return types.some(function(type) {
return type === service.type;
});
}
var serviceHelper = (function() {
return {
add: function(record) {
// try update existing service
var index = availableServiceRecords.findIndex(function(activeService) {
return activeService.id === record.id;
});
if (index >= 0) {
availableServiceRecords[index].update(record);
return;
}
// new service available
availableServiceRecords.push(record);
activeServiceManagers.forEach(function(manager) {
// wiring to existed NetworkService objects
var index = manager._services.findIndex(function(service) {
return service.id === record.id;
});
if (index >= 0 && manager._services[index].online === false) {
var service = manager._services[index];
service._record = record;
service.online = true;
setTimeout(function() {
service.dispatchEvent(new CustomEvent('available',
{ bubbles: false,
cancelable: false,
detail: {target: service},
}));
}, 0);
}
// notify correspoding service managers
if (_hasRequestedType(record, manager._types)) {
// XXX should we append new service to existing manager?
if (index < 0) {
var netService = new NetworkService(record);
manager._append(netService);
}
manager.servicesAvailable++;
setTimeout(function() {
manager.dispatchEvent(new CustomEvent('servicefound',
{ bubbles: false,
cancelable: false,
detail: {target: manager},
}));
}, 0);
}
});
},
remove: function(record) {
var index = availableServiceRecords.findIndex(function(activeService) {
return activeService.id === record.id;
});
if (index >= 0) {
var activeService = availableServiceRecords[index];
activeServiceManagers.forEach(function(manager) {
var index = manager._services.findIndex(function(service) {
return service.id === record.id;
});
if (index >= 0 && manager._services[index].online === true) {
// notify service unavailable
var service = manager._services[index];
service.online = false;
setTimeout(function() {
service.dispatchEvent(new CustomEvent('unavailable',
{ bubbles: false,
cancelable: false,
detail: {target: service},
}));
}, 0);
}
// notify service lost
if (_hasRequestedType(activeService, manager._types)) {
manager.servicesAvailable--;
setTimeout(function() {
manager.dispatchEvent(new CustomEvent('servicelost',
{ bubbles: false,
cancelable: false,
detail: {target: manager},
}));
}, 0);
}
});
// XXX unregister UPnP event
if (activeService.eventsUrl) {
console.log('unregister event for ' + activeService.eventsUrl);
}
// remove from available service list
availableServiceRecords.splice(index, 1);
}
},
};
})();
var availableServiceRecords = []; // list of NetworkService
var activeServiceManagers = []; // list of NetworkServices //XXX need weak reference
var targetRegister = {
'upnp:': {
register: function(target) {
SimpleServiceDiscovery.registerTarget(target.substr(5)); // remove upnp: prefix
},
search: function() {
SimpleServiceDiscovery.search();
}
},
};
function getNetworkServices(type) {
const UNKNOWN_TYPE_PREFIX_ERR = 'UnknownTypePrefixError';
const PERMISSION_DENIED_ERR = 'PermissionDeniedError';
function _isValidServiceType (type) {
return type && Object.keys(targetRegister).some(function(prefix) { return type.startsWith(prefix); });
}
function _register(types) {
var needSearch = {};
types.forEach(function(type) {
var found = Object.keys(targetRegister).find(function(prefix) { return type.startsWith(prefix); });
if (found) {
targetRegister[found].register(type);
if (!needSearch.hasOwnProperty(found)) {
needSearch[found] = true;
}
}
});
// trigger search on the discovery service if new search target
// is registered
Object.keys(needSearch).forEach(function(type) {
targetRegister[type].search();
});
}
var requestedControlTypes = [].concat(type).filter(_isValidServiceType);
if (requestedControlTypes.length === 0) {
return Promise.reject(new DOMError(UNKNOWN_TYPE_PREFIX_ERR));
}
_register(requestedControlTypes);
var allPromises = [];
availableServiceRecords.forEach(function(service) {
if (_hasRequestedType(service, requestedControlTypes)) {
// XXX doesn't check CORS yet
// create a new instance of NetworkService
allPromises.push(Promise.resolve(new NetworkService(service)));
}
});
return Promise.all(allPromises).then(function(servicesFound) {
var services = servicesFound.filter(function(service) {
//XXX might also filter by user preference
return service !== null;
});
for (var i = 0; i < services.length; i++) {
if (services[i]._record.eventsUrl) {
//XXX perform UPnP event subscription
console.log('register for ' + services[i]._record.eventsUrl);
}
}
var serviceManager = new NetworkServices(requestedControlTypes, services);
serviceManager.servicesAvailable = availableServiceRecords.reduce(function(count, service) {
return (_hasRequestedType(service, requestedControlTypes)) ? count + 1 : count;
}, 0);
activeServiceManagers.push(serviceManager);
return Promise.resolve(serviceManager);
});
}
// export NetworkServices and NetworkServices as pre-defined types
global.NetworkService = NetworkService;
global.NetworkServices = NetworkServices;
navigator.getNetworkServices = getNetworkServices;
global.addEventListener('unload', function() {
delete navigator.getNetworkServices;
});
})(window);