"use strict";

(function (global) {
    
    /** the call function this library will call to send messages to the server, it is recommended
      * to replace this in the resulting object with your method
      */
    const libCall = function (method, args) { /* replace this method and send data to server */ }

    var decode = function(input) {
        // Replace non-url compatible chars with base64 standard chars
        input = input
            .replace(/-/g, '+')
            .replace(/_/g, '/');
    
        // Pad out with standard base64 required padding characters
        var pad = input.length % 4;
        if(pad) {
            if(pad === 1) {
            throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
            }
            input += new Array(5-pad).join('=');
        }
    
        return input;
    }

    function base64ToBuffer(base64) {
        return new Uint8Array(atob(base64).split('').map(c => c.charCodeAt(0)));
    }

    function base64UrlToBuffer(base64Url) {
        return base64ToBuffer(decode(base64Url));
    }

    // this line is from https://raw.githubusercontent.com/WebReflection/uint8-to-base64/master/min.js
    const uint8ToBase64=function(r){"use strict";function n(r){return r.charCodeAt(0)}var e=String.fromCharCode;return r.decode=function(r){return Uint8Array.from(atob(r),n)},r.encode=function(r){for(var n=[],t=0,o=r.length;t<o;t++)n.push(e(r[t]));return btoa(n.join(""))},r}({});

    function bufferToBase64(buffer) {
        return uint8ToBase64.encode(new Uint8Array(buffer));
        // return btoa(new TextDecoder('utf8').decode(buffer));
    }

    async function receive(method, args) {
        switch (method) {
            case 'register': {
                const { options, user } = args;
                options.challenge = base64UrlToBuffer(options.challenge);
                options.user.id =   base64UrlToBuffer(options.user.id);

                const credential = await navigator.credentials.create({
                    publicKey: options
                });

                var data = {
                    id: decode(credential.id),
                    rawId: bufferToBase64(credential.rawId),
                    type: credential.type,
                    response: {
                        attestationObject: bufferToBase64(credential.response.attestationObject),
                        clientDataJSON: bufferToBase64(credential.response.clientDataJSON)
                    }
                };

                global.fido2.libCall('register', { user, data });

                break;
            }
            case 'login': {
                const { options, user } = args;
                options.challenge = base64UrlToBuffer(options.challenge);
                for (var i = 0; i < options.allowCredentials.length; ++i)
                    options.allowCredentials[i].id = base64UrlToBuffer(options.allowCredentials[i].id);

                const credential = await navigator.credentials.get({ publicKey: options });

                var data = {
                    id: decode(credential.id),
                    rawId: bufferToBase64(credential.rawId),
                    type: credential.type,
                    response: {
                        authenticatorData: bufferToBase64(credential.response.authenticatorData),
                        clientDataJSON: bufferToBase64(credential.response.clientDataJSON),
                        signature: bufferToBase64(credential.response.signature),
                        userHandle: bufferToBase64(credential.response.userHandle)
                    }
                };

                global.fido2.libCall('login', { user, data });

                break;
            }
            default: {
                console.log(`unsupported fido2 method: ${method}`);
            }
        }
    }

    global.fido2 = {
        libCall,
        receive
    }

})(this);
