最近整理一篇关于 一些 浏览器html5 相关api 的权限的东西 虽然大部分都可以用了 但是基本上还是草案居多
本文所有的demo都在 这里 blog-demo/permission 代码在这github.com/thesadabc/blog-demo
有几个权限的demo还没有写好 之后会单独写出来
w3c.github.io/permissions: Editor’s Draft, 17 October 2016
事实上不止链接中的这几个权限, 比如usb
, 本文就只对下面列出的这几个权限进行介绍
1 2 3 4 5 6 7 8 9 10 11 12 13
| enum PermissionName { "geolocation", "notifications", "push", "midi", "camera", "microphone", "speaker", "device-info", "background-sync", "bluetooth", "persistent-storage" }
|
push
background-sync
需要配合service worker
工作
notifications
在service worker
与浏览器环境中有不同
检查权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| navigator.permissions.query({ name: "notifications", userVisibleOnly: false, sysex: false, deviceId: null, }).then((permissionStatus) => { let { state } = permissionStatus; permissionStatus.onchange = function () {} });
navigator.permissions.request({})
|
geolocation
www.w3.org/TR/geolocation-API: W3C Recommendation 8 November 2016
虽然这个版本是2016年11月8日出的第二版 才出没多久 按照其他权限的实现方式 估计这个方案会改成Promise
的方式 而且会增加一个专门来请求权限的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| navigator.geolocation.getCurrentPosition((coords) => { let { timestamp, coords:{ accuracy, latitude, longitude, altitude, altitudeAccuracy, heading, speed } } = coords; }, (err) => { let { code, message } = err;
console.log(err) }, { enableHighAccuracy: false, timeout: 0xFFFFFFFF, maximumAge: 0 });
|
Notification
notifications.spec.whatwg.org: Living Standard — Last Updated 15 February 2017
通知有两套实现方案 一个是在浏览器中直接调用构造函数生成通知 另外一个是中调用service worker
里的serviceWorkerRegistration
对象上的构造方法
两者有两个区别
- 事件监听方式不同 第二种可以将事件注册在全局对象
ServiceWorkerGlobalScope
上 - 通知属性不同 第二种比第一种多了
actions
属性 可以在通知框上添加额外的按钮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| Notification.requestPermission().then((perm)=>{ if (perm === "denied") return log("permission denied")
let noti = new Notification("title", { dir: "auto", lang: "", body: "body", tag: "tag", image: "", icon: "", badge: "", sound: null, vibrate: null,
timestamp: 123123,
data: null, }); let { renotify, silent, requireInteraction, } = noti;
noti.addEventListener("click", function(e){ }) noti.addEventListener("error", function(e){ }) noti.addEventListener("show", function(e){ }) noti.addEventListener("close", function(e){ })
self.registration.showNotification("title", { actions: [ { 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
的推送有关 内容比较复杂 之后我再单独开一篇文章写这个吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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"));
}).catch((error) => { console.log(error); });
self.onpush = ({data}) => { console.log(data);
data.arrayBuffer().then((ab) => { }); }; self.onpushsubscriptionchange = ({newSubscription, oldSubscription}) => {}
|
midi
web midi: W3C Working Draft 17 March 2015
获取设备上的MIDI
接口 多用于音频设备(主机上也留有这种接口 给键盘鼠标用的)
这个api太专业了 我这里没有测试的设备 所以没法试这个代码 而且数据请求都是直接传送二进制数据 所以也比较头疼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| navigator.requestMIDIAccess({sysex: true}).then((midiAccess) =>{ let { inputs:inputMap, outputs:outputMap, sysexEnabled } = midiAccess; midiAccess.onstatechange = ({port}) => {};
outputMap[id1].onstatechange = ({port}) => { }; inputMap[id1].onstatechange =({port}) => { };
for(let [id, output] of outputMap.entries()){ output.clear(); output.send(new Uint8Array([ 0x90, 0x45, 0x7f ]), Date.now()); }
for(let [id, input] of inputMap.entries()){ input.onmidimessage = ({data, receivedTime}) => {}; }
return inputMap[id1].open().then((port) => { }); });
|
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
属性 设置播放源
1 2 3
| video.srcObject = stream;
video.src = URL.createObjectURL(stream);
|
sinkId
属性 设置播放设备
1
| audio.setSinkId(deviceId);
|
capture
属性 选择摄像头拍摄内容作为文件 移动端较为常见 而且常内置于type="file"
标签中
1
| <input type="file" accept="video/*, image/*, audio/*" capture>
|
allowusermedia
属性
1
| iframe.allowUserMedia = true
|
MediaStream Image Capture 图像捕获相关的接口
除了这些之外 这个标准还和webrtc标准有一点联系 可以研究一下
标准里涉及到了多个非常非常相似的几个数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| 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; height; aspectRatio; frameRate; facingMode; volume; sampleRate; sampleSize; echoCancellation; latency; channelCount; deviceId; groupId; }; Capabilities: { width; height; aspectRatio; frameRate; facingMode; volume; sampleRate; sampleSize; echoCancellation; latency; channelCount; deviceId; groupId; }
ConstraintSet: { width: null, height: null, aspectRatio: null, frameRate: null, facingMode: null, volume: null, sampleRate: null, sampleSize: null, echoCancellation: null, latency: null, channelCount: null, deviceId: null, groupId: null, }
Constraints:{ ...ConstraintSet advanced:[ConstraintSet] }
|
这三个结构 相当于Settings
是最基础的 Capabilities
在前者只是给每个属性增加了扩展: 字符串可以使用数组, 数值可以给范围 ConstraintSet
再进行扩展 给每个属性增加了预期值和实际值 Constraints
再在前者上增加了一个属性 可以设置为同结构的数组 几个数据结构用的地方也不太一样 要注意
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| navigator.mediaDevices.enumerateDevices().then(([device])=>{ let { deviceId, groupId, kind, label } = device.toJSON() audioElement.setSinkId(deviceId).then(() => {}); let capabilities= device.getCapabilities() }); navigator.mediaDevices.ondevicechange = () => {};
let supportedConstraints = navigator.mediaDevices.getSupportedConstraints(); if(!supportedConstraints["width"] || !supports["height"]) { } navigator.mediaDevices.getUserMedia({ video: { width: {min: 320, ideal: 1280, max: 1920}, height: {min: 240, ideal: 720, max: 1080}, }, audio: true, }).then((stream) => {
videoElement.srcObject = stream;
let {id, active} = stream; let stream2 = stream.clone(); stream.onaddtrack = ({track})=>{}; stream.onremovetrack = ({track})=>{};
let tracksList = stream.getAudioTracks(); let track = stream.getTrackById(tracksList[0].id); stream.removeTrack(track); stream.addTrack(track);
let { kind, id, label, enabled, muted, readyState, } = track; track.onended = (e)=>{} track.onmute = (e)=>{} track.onunmute = (e)=>{} let track2 = track.clone() let imageCapture = new ImageCapture(track); imageCapture.takePhoto().then(blob => { image.src = URL.createObjectURL(blob); }); setInterval(() => { imageCapture.grabFrame().then(frame => { canvas.width = imgData.width; canvas.height = imgData.height; canvas.getContext('2d').drawImage(imgData, 0, 0); }) }, 1000);
track.onoverconstrained = ({error})=>{}; track.applyConstraints(Constraints).then(() => {}); track.stop() });
|
background-sync
wicg.github.io/BackgroundSync/spec: Draft Community Group Report, 2 August 2016
permission:
Note: Background sync SHOULD be enabled by default. Having the permission denied is considered an exceptional case.
browser
与ServiceWorker
之间进行的相当简单的单向通信 不能传数据
当一个耗时的重要操作还没有完成的时候 如果用户把浏览器关了 就会造成数据丢失什么的 为了解决这个问题可以吧这些操作放到ServiceWorker
中进行处理 这样
其实感觉这个权限有点点奇怪
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| navigator.serviceWorker.register("worker.js").then((serviceWorkerRegistration) => { let syncManager = serviceWorkerRegistration.sync;
syncManager.register("send-chats").then(() => {}); syncManager.getTags().then(([tag1]) => {}); });
self.addEventListener("sync", function({tag, lastChance}) { if(tag === "send-chats") { doImportantThing().catch((err) => { if(lastChance){ } }); } });
|
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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| navigator.storage.persist().then((persisted) => { if(persisted) { } });
navigator.storage.persisted().then((persisted) => { if(persisted) { } });
navigator.storage.estimate().then(({quota, usage}) => { })
|