WEB API权限整理

最近整理一篇关于 一些 浏览器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工作

notificationsservice 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对象上的构造方法

两者有两个区别

  1. 事件监听方式不同 第二种可以将事件注册在全局对象ServiceWorkerGlobalScope
  2. 通知属性不同 第二种比第一种多了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(): 获取设备媒体输入数据 对应的cameramicrophone权限 这个函数做过一次相当大的调整 原本是直接在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

permission:

Note: Background sync SHOULD be enabled by default. Having the permission denied is considered an exceptional case.

browserServiceWorker之间进行的相当简单的单向通信 不能传数据

当一个耗时的重要操作还没有完成的时候 如果用户把浏览器关了 就会造成数据丢失什么的 为了解决这个问题可以吧这些操作放到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}) => {
        // 总空间,  已使用空间
    })

原文链接

转载于:https://my.oschina.net/thesadabc/blog/896257

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值