最近整理一篇关于 一些 浏览器html5 相关api 的权限的东西 虽然大部分都可以用了 但是基本上还是草案居多
本文所有的demo都在 这里 blog-demo/permission 代码在这github.com/thesadabc/blog-demo
有几个权限的demo还没有写好 之后会单独写出来
w3c.github.io/permissions: Editor’s Draft, 17 October 2016
事实上不止链接中的这几个权限, 比如usb
, 本文就只对下面列出的这几个权限进行介绍
enum PermissionName {
"geolocation",
"notifications",
"push",
"midi",
"camera",
"microphone",
"speaker",
"device-info",
"background-sync",
"bluetooth",
"persistent-storage"
}
push
background-sync
需要配合service worker
工作
notifications
在service worker
与浏览器环境中有不同
检查权限
navigator.permissions.query({
name: "notifications", // ["notifications", "push", "midi", "background-sync"]
//not support now: ["camera", "microphone", "speaker", "device-info", "bluetooth", "persistent-storage"]
userVisibleOnly: false, // for "push"
sysex: false, // for "midi"
deviceId: null, // for ["camera", "microphone", "speaker"], 可以从`navigator.mediaDevice`接口获取
}).then((permissionStatus) => {
let {
state // ["granted", "denied", "prompt"]
} = permissionStatus;
permissionStatus.onchange = function () {}
});
// 请求权限 目前没有浏览器实现这个接口 所以在各个接口有自己的实现
navigator.permissions.request({})
geolocation
www.w3.org/TR/geolocation-API: W3C Recommendation 8 November 2016
虽然这个版本是2016年11月8日出的第二版 才出没多久 按照其他权限的实现方式 估计这个方案会改成Promise
的方式 而且会增加一个专门来请求权限的接口
// 调用的时候会向用户请求权限
navigator.geolocation.getCurrentPosition((coords) => {
let {
timestamp,
coords:{
accuracy,
latitude,
longitude,
altitude,
altitudeAccuracy,
heading, // 0 - 360 从北开始的顺时针
speed
}
} = coords;
}, (err) => {
let {
code, // PERMISSION_DENIED = 1; POSITION_UNAVAILABLE = 2; TIMEOUT = 3;
message
} = err;
console.log(err)
}, {
enableHighAccuracy: false,
timeout: 0xFFFFFFFF,
maximumAge: 0
});
// let watchId = navigator.geolocation.watchPosition(()=>{}, ()=>{}, {})
// navigator.geolocation.clearWatch(watchId)
Notification
notifications.spec.whatwg.org: Living Standard — Last Updated 15 February 2017
通知有两套实现方案 一个是在浏览器中直接调用构造函数生成通知 另外一个是中调用service worker
里的serviceWorkerRegistration
对象上的构造方法
两者有两个区别
- 事件监听方式不同 第二种可以将事件注册在全局对象
ServiceWorkerGlobalScope
上 - 通知属性不同 第二种比第一种多了
actions
属性 可以在通知框上添加额外的按钮
// 请求通知权限
Notification.requestPermission().then((perm)=>{
// "default", "denied", "granted"
if (perm === "denied") return log("permission denied")
let noti = new Notification("title", {
dir: "auto", //"auto", "ltr", "rtl"
lang: "", // en
body: "body",
tag: "tag", // 每个通知的标识 如果前一个相同tag的通知未消失 则本通知直接替换前一个 不触发新的事件 如 前一个的`close`事件与当前的`show`事件
image: "", // 资源地址
icon: "", // 资源地址
badge: "", // 资源地址 // 这啥??
sound: null, // 资源地址
vibrate: null, // [100, 50], 震动100ms 暂停50ms
timestamp: 123123, // ???
data: null,
});
let {
renotify, // 该通知是否替换前一个通知
silent, // 是否静音 即本通知无声音和震动
requireInteraction, // 是否可以被用户处理
} = noti;
noti.addEventListener("click", function(e){
// noti.close()
})
noti.addEventListener("error", function(e){
})
noti.addEventListener("show", function(e){
})
noti.addEventListener("close", function(e){
})
// in service worker
self.registration.showNotification("title", {
// ...
actions: [ // 仅限 server worker
{
action: "action1",
title: "actionTitle",
icon: "" // 资源地址
}
]
}).then(() => {});
self.registration.getNotifications({tag:"tag"}).then(([]) => {});
self.addEventListener("notificationclick", function (e){
console.log("notificationclick")
console.log(e.action)
e.notification.close();
});
self.addEventListener("notificationclose", function (e){
console.log("notificationclose")
});
});
push
www.w3.org/TR/push-api/: W3C Working Draft 22 February 2017
相关内容: appmanifest Progressive Web Apps
这个api和Progressive Web Apps
的推送有关 内容比较复杂 之后我再单独开一篇文章写这个吧
// in browser
navigator.serviceWorker.register("worker.js").then((serviceWorkerRegistration) => {
return serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: false,
applicationServerKey: null
});
}).then((pushSubscription) => {
console.log(pushSubscription.endpoint);
console.log(pushSubscription.options);
console.log(pushSubscription.getKey("p256dh"));
console.log(pushSubscription.getKey("auth"));
// return pushSubscription.unsubscribe()
}).catch((error) => {
console.log(error);
});
// in worker.js
self.onpush = ({data}) => {
console.log(data);
data.arrayBuffer().then((ab) => { });
// return data.blob();
// return data.json();
// return data.text()
};
self.onpushsubscriptionchange = ({newSubscription, oldSubscription}) => {}
midi
web midi: W3C Working Draft 17 March 2015
获取设备上的MIDI
接口 多用于音频设备(主机上也留有这种接口 给键盘鼠标用的)
这个api太专业了 我这里没有测试的设备 所以没法试这个代码 而且数据请求都是直接传送二进制数据 所以也比较头疼
navigator.requestMIDIAccess({sysex: true}).then((midiAccess) =>{
let { inputs:inputMap, outputs:outputMap, sysexEnabled } = midiAccess;
midiAccess.onstatechange = ({port}) => {};
/****
* inputMap/outputMap: {
* id1:{
* id1, // unique ID of the port
* manufacturer,
* name,
* type, // ["output", "input"]
* version,
* state, // ["disconnected", "connected"]
* connection, //["open", "closed", "pending"]
* }
* }
**/
outputMap[id1].onstatechange = ({port}) => {
// port === outputMap[id1]
};
inputMap[id1].onstatechange =({port}) => {
// port === inputMap[id1]
};
for(let [id, output] of outputMap.entries()){
output.clear();
output.send(new Uint8Array([ 0x90, 0x45, 0x7f ]), Date.now());
// output.send([ 0x90, 0x45, 0x7f ], Date.now());
}
for(let [id, input] of inputMap.entries()){
input.onmidimessage = ({data, receivedTime}) => {};
}
return inputMap[id1].open().then((port) => {
// port === inputMap[id1]
});
// return inputMap[id1].close();
// return outputMap[id1].close();
// return outputMap[id1].open();
});
media: camera, microphone, speaker, device-info
www.w3.org/TR/mediacapture-streams: W3C Candidate Recommendation 19 May 2016
www.w3.org/TR/audio-output: W3C Working Draft 15 December 2016
主要涉及两个函数
navigator.mediaDevices.enumerateDevices()
: 查询设备信息 和device-info
权限相关 一般都是允许的
navigator.mediaDevices.getUserMedia()
: 获取设备媒体输入数据 对应的camera
和microphone
权限 这个函数做过一次相当大的调整 原本是直接在navigator
下的navigator.getUserMedia
而且是回调式的 现在是基于Promise
的异步
关于speaker
权限 暂时还没有去研究 看接口应该是能指定某个音箱设备播放声音 而且有单独的标准audio-output
除了浏览器的接口外 还涉及HTMLMediaElement相关的东西
srcObject
属性 设置播放源
video.srcObject = stream;
// or this
video.src = URL.createObjectURL(stream);
sinkId
属性 设置播放设备
audio.setSinkId(deviceId);
capture
属性 选择摄像头拍摄内容作为文件 移动端较为常见 而且常内置于type="file"
标签中
<input type="file" accept="video/*, image/*, audio/*" capture>
allowusermedia
属性
iframe.allowUserMedia = true
MediaStream Image Capture 图像捕获相关的接口
除了这些之外 这个标准还和webrtc标准有一点联系 可以研究一下
标准里涉及到了多个非常非常相似的几个数据结构
SupportedConstraints { // 各个属性是否支持
width: true,
height: true,
aspectRatio: true,
frameRate: true,
facingMode: true,
volume: true,
sampleRate: true,
sampleSize: true,
echoCancellation: true,
latency: true,
channelCount: true,
deviceId: true,
groupId: true,
};
Settings { // 这三个结构到底是想怎么样啊!!!
width; // 1000
height; // 1000
aspectRatio; // 1000
frameRate; // 1000
facingMode; // string ["user", "environment", "left", "right"]
volume; // 1000
sampleRate; // 1000
sampleSize; // 1000
echoCancellation; // boolean
latency; // 1000
channelCount; // 1000
deviceId; // "string"
groupId; // "string"
};
Capabilities: { // 和ConstraintSet很像
width; // 1000 or {min,max}
height; // 1000 or {min,max}
aspectRatio; // 1000 or {min,max}
frameRate; // 1000 or {min,max}
facingMode; // [string]
volume; // 1000 or {min,max}
sampleRate; // 1000 or {min,max}
sampleSize; // 1000 or {min,max}
echoCancellation; //[boolean]
latency; // false or [boolean]
channelCount;// 1000 or {min,max}
deviceId; // string
groupId; // string
}
ConstraintSet: { // 这个结构太tm复杂了
width: null, // only video, 10000 or {min, max, exact, ideal}
height: null, // only video, 10000 or {min, max, exact, ideal}
aspectRatio: null, // only video, 10000 or {min, max, exact, ideal}
frameRate: null, // only video, 10000 or {min, max, exact, ideal}
facingMode: null, // only video, string or [string] or {exact, ideal}
volume: null, // 10000 or {min, max, exact, ideal}
sampleRate: null, // 10000 or {min, max, exact, ideal}
sampleSize: null, // 10000 or {min, max, exact, ideal}
echoCancellation: null,// boolean or {exact, ideal}
latency: null, // 10000 or {min, max, exact, ideal}
channelCount: null,// 10000 or {min, max, exact, ideal}
deviceId: null, // string or [string] or {exact, ideal}
groupId: null, // string or [string] or {exact, ideal}
}
Constraints:{ // 这个结构也是神了.....
...ConstraintSet
advanced:[ConstraintSet]
}
这三个结构 相当于Settings
是最基础的 Capabilities
在前者只是给每个属性增加了扩展: 字符串可以使用数组, 数值可以给范围 ConstraintSet
再进行扩展 给每个属性增加了预期值和实际值 Constraints
再在前者上增加了一个属性 可以设置为同结构的数组 几个数据结构用的地方也不太一样 要注意
// device-info
navigator.mediaDevices.enumerateDevices().then(([device])=>{
let {
deviceId,
groupId, // 同一个物理设备上的设备的值相同
kind, // ["audiooutput", "audioinput", "videoinput"]
label
} = device.toJSON()
// set audio speaker
audioElement.setSinkId(deviceId).then(() => {});
// let did = audioEle.sinkId;
// input only
let capabilities= device.getCapabilities()
});
navigator.mediaDevices.ondevicechange = () => {};
let supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
if(!supportedConstraints["width"] || !supports["height"]) {
// if not support width or height
}
navigator.mediaDevices.getUserMedia({
video: { // constraints or true
width: {min: 320, ideal: 1280, max: 1920},
height: {min: 240, ideal: 720, max: 1080},
},
audio: true, // constraints or true
}).then((stream) => {
videoElement.srcObject = stream;
let {id, active} = stream;
let stream2 = stream.clone();
stream.onaddtrack = ({track})=>{};
stream.onremovetrack = ({track})=>{};
let tracksList = stream.getAudioTracks();
// stream.getVideoTracks();
// stream.getTracks();
let track = stream.getTrackById(tracksList[0].id);
stream.removeTrack(track);
stream.addTrack(track);
let {
kind, // ["video", "audio"]
id,
label,
enabled,
muted,
readyState, // ["live", "ended"]
} = track;
track.onended = (e)=>{}
track.onmute = (e)=>{}
track.onunmute = (e)=>{}
let track2 = track.clone()
// only video
let imageCapture = new ImageCapture(track);
// take photo
imageCapture.takePhoto().then(blob => {
image.src = URL.createObjectURL(blob);
});
// draw to cavas
setInterval(() => {
imageCapture.grabFrame().then(frame => {
canvas.width = imgData.width;
canvas.height = imgData.height;
canvas.getContext('2d').drawImage(imgData, 0, 0);
})
}, 1000);
// Constrainable Pattern
track.onoverconstrained = ({error})=>{};
track.applyConstraints(Constraints).then(() => {});
// let capabilities = track.getCapabilities()
// let constraints = track.getConstraints()
// let settings = track.getSettings()
track.stop()
});
background-sync
wicg.github.io/BackgroundSync/spec: Draft Community Group Report, 2 August 2016
Note: Background sync SHOULD be enabled by default. Having the permission denied is considered an exceptional case.
browser
与ServiceWorker
之间进行的相当简单的单向通信 不能传数据
当一个耗时的重要操作还没有完成的时候 如果用户把浏览器关了 就会造成数据丢失什么的 为了解决这个问题可以吧这些操作放到ServiceWorker
中进行处理 这样
其实感觉这个权限有点点奇怪
// in browser
navigator.serviceWorker.register("worker.js").then((serviceWorkerRegistration) => {
let syncManager = serviceWorkerRegistration.sync;
syncManager.register("send-chats").then(() => {});
syncManager.getTags().then(([tag1]) => {});
});
// in worker.js
self.addEventListener("sync", function({tag, lastChance}) {
if(tag === "send-chats") {
doImportantThing().catch((err) => {
if(lastChance){
// 本次是最后一次重试
// 可以使用 notifications 来提示用户
}
});
}
});
bluetooth
webbluetoothcg.github.io/web-bluetooth: Draft Community Group Report, 15 February 2017
社区 www.w3.org/community/web-bluetooth
相当复杂 另起一篇 而且没有测试设备 这个月初的时候官方才刚刚出了一个demo 还挺复杂
其实本篇文章就是从这个demo开始的 本来想研究下WebBluetooth
然后涉及到浏览器权限 然后干脆就整理一个吧 于是就写了这篇整理权限的东西
persistent-storage
storage.spec.whatwg.org: Living Standard — Last Updated 23 February 2017
似乎这个权限没有明显的代码实例 只是一个普通的查询用的权限 根据这篇文章的回复persistent-storage 似乎认为这个接口是用来查询浏览器端数据存储是否为永久性的. 如果权限查询结果为true
表明这些存储方式存储的数据 在系统存储空间充足的情况下 不会被浏览器清理掉 具体有哪些存储方式 文档有说明storage.spec.whatwg.org/#infrastructure 包括网络请求的缓存 cookie IndexDB LocalStorage等
一个同源站点关联里一个站点存储单元
(site storage units) 每个站点存储单元
包含了一个单个的空间
(box)
// 在`persistent-storage`权限为`granted`的情况下 将`站点存储单元`的`存储空间`类型转换为`persistent box`
navigator.storage.persist().then((persisted) => {
if(persisted) {
/* … */
}
});
// 查询`站点存储单元`的`存储空间`类型是否为`persistent box`
navigator.storage.persisted().then((persisted) => {
if(persisted) {
/* … */
}
});
navigator.storage.estimate().then(({quota, usage}) => {
// 总空间, 已使用空间
})