0

I'm building my first Firefox Web Extension, and I am integrating with an API using OAuth.

What happens is when the user clicks the icon in the toolbar, it gives them an authentication number to enter into a web page. They then do this, authorize my app and it stores an access token.

Where I'm getting stuck is currently my code relies on the popup window staying open to poll for a response from the responding API. I'm assuming I need to run the poll in a background file, but I'm not sure how I would do this.

My popup.js

// Set Vars
var successComp = 0;
// Poll to check for authentication
function pollAccessToken(device_code, interval) {
    var request = new XMLHttpRequest();
    request.open('POST', 'https://private-anon-535ecb43fa-trakt.apiary-mock.com/oauth/device/token');
    request.setRequestHeader('Content-Type', 'application/json');
    request.onreadystatechange = function () {
        if (this.readyState === 4) {
            switch (this.status) {
                case 200:
                    // Get Response and put in array
                    var response = JSON.parse(this.responseText);
                    // Save Access Token and Refresh Token to storage
                    chrome.storage.local.set({"access_token": response.access_token, "refresh_token": response.refresh_token});
                    // Set success notification
                    chrome.runtime.sendMessage({"title": "Trakt for IMDb Authentication Success", "content": "Awesome, you've authenticated your Trakt account! Enjoy using Trakt for IMDb."});
                    // Set successComp to 1 so clearTimeout doesn't trigger
                    successComp = 1;
                    break;
                case 400:
                    // Request still pending
                    setTimeout(pollAccessToken.bind(null, device_code).bind(null, interval), interval * 1000);
                    break;
                case 404:
                    // Display Request not found/invalid error
                    chrome.runtime.sendMessage({"title": "Trakt for IMDb Authentication Error", "content": "Whoops, looks like there was an error authenticating your Trakt account. Please try again."});
                    break;
                case 409:
                    // Code already used
                    chrome.runtime.sendMessage({"title": "Trakt for IMDb already approved", "content": "Whoops, looks like you've already approved this code. If you continue to have issues, please try again."});
                    break;
                case 410:
                    // Code Expired
                    chrome.runtime.sendMessage({"title": "Trakt for IMDb Authentication Token Error", "content": "Whoops, looks like your token expired and there was an error authenticating your Trakt account. Please try again"});
                    break;
                case 418:
                    // Code Denied
                    chrome.runtime.sendMessage({"title": "Trakt for IMDb Authentication Denied", "content": "Oh no, it looks like you denied us access to your account! If you didn't mean to do this, please try again."});
                    break;
                case 429:
                    // Slow down polling
                    setTimeout(pollAccessToken.bind(null, device_code).bind(null, interval), interval * 1100);
                    break;
                default:
                    // Request still pending
                    setTimeout(pollAccessToken.bind(null, device_code).bind(null, interval), interval * 1000);
                    break;
            }
        }
    };
    var body = {
        'code': device_code,
        'client_id': APP_KEY,
        'client_secret': APP_SEC
    };
    request.send(JSON.stringify(body));
}
// Get and display authentication
function outputAuth() {
    var request = new XMLHttpRequest();
    request.open('POST', 'https://private-anon-535ecb43fa-trakt.apiary-mock.com/oauth/device/code');
    request.setRequestHeader('Content-Type', 'application/json');
    request.onreadystatechange = function () {
        if (this.readyState === 4) {
            // Get Response and put in array
            var response = JSON.parse(this.responseText);
            // Output authentication data
            document.body.innerHTML = '<section id="traktLogo"><img src="../icons/trakt-logo.png" width="100px" height="100px" alt="Trakt" /></section><h1>Trakt Authentication</h1><h2>Enter the following code into the activation page.</h2><code>' + response.user_code + '</code><a href="' + response.verification_url + '" id="activation" target="_newtab">Activation Page</a>';
            var pollAccess = pollAccessToken(response.device_code, response.interval);
            setTimeout(stopPolling, response.expires_in * 1000);
        }
    };
    var body = {
        'client_id': APP_KEY
    };
    request.send(JSON.stringify(body));
}
// Stop Polling Function
function stopPolling() {
    if (successComp === 0) {
        // Response polling expired
        clearTimeout(pollAccess);
        chrome.runtime.sendMessage({"title": "Trakt for IMDb Authentication Timed Out", "content": "Whoops, looks like you took too long in authenticating your Trakt account. Please try again."});
    }
}
// Check if Access Token Exists
chrome.storage.local.get("access_token", function (result) {
    if (result.access_token && typeof result.access_token !== undefined) {
        // Output Profile Page
        document.body.innerHTML = '<p>Profile Page</p>';
    } else {  
        // Output Authenctication Page
        outputAuth();
    }
});

My background.js

// Display a Notification
function notify(message) {
    chrome.notifications.create({
        "type": "basic",
        "title": message.title,
        "message": message.content,
        "iconUrl": chrome.extension.getURL("icons/icon-256.png")
    });
}
/*
Assign `notify()` as a listener to messages from the content script.
*/
chrome.runtime.onMessage.addListener(notify);

I'm happy enough to send the polling code to the background.js file, I'm just not quite sure how I would trigger it. It's also very important that I have the code that will stop the pollAccessToken function from running after x amount of minutes as specified by the API docs.

Full source code can be previewed at Trakt for IMDb on Github

Polling API docs can be found at Trakt.tv API on Apiary

dpDesignz
  • 1,909
  • 10
  • 34
  • 70

1 Answers1

0

I think I've got it working with the following script. If anyone can tell me why the following WOULDN'T work how I want it to would be much appreciated :)

popup.js

// Get and display authentication
function outputAuth() {
    var request = new XMLHttpRequest();
    request.open('POST', 'https://private-anon-535ecb43fa-trakt.apiary-mock.com/oauth/device/code');
    request.setRequestHeader('Content-Type', 'application/json');
    request.onreadystatechange = function () {
        if (this.readyState === 4) {
            // Get Response and put in array
            var response = JSON.parse(this.responseText);
            // Output authentication data
            document.body.innerHTML = '<section id="traktLogo"><img src="../icons/trakt-logo.png" width="100px" height="100px" alt="Trakt" /></section><h1>Trakt Authentication</h1><h2>Enter the following code into the activation page.</h2><code>' + response.user_code + '</code><a href="' + response.verification_url + '" id="activation" target="_newtab">Activation Page</a>';
            chrome.runtime.sendMessage({"type": "pollRequest", "device_code": response.device_code, "interval": response.interval, "expires_in": response.expires_in});
        }
    };
    var body = {
        'client_id': APP_KEY
    };
    request.send(JSON.stringify(body));
}
// Check if Access Token Exists
chrome.storage.local.get("access_token", function (result) {
    if (result.access_token && typeof result.access_token !== undefined) {
        // Output Profile Page
        document.body.innerHTML = '<p>Profile Page</p>';
    } else {  
        // Output Authenctication Page
        outputAuth();
    }
});

background.js

// key.js - Trakt for IMDb by dpDesignz
var APP_KEY = '';
var APP_SEC = '';
// Display a Notification
function notify(message) {
    chrome.notifications.create({
        "type": "basic",
        "title": message.title,
        "message": message.content,
        "iconUrl": chrome.extension.getURL("icons/icon-256.png")
    });
}
// Set Vars
var successComp = 0;
// Poll to check for authentication
function pollAccessToken(device_code, interval) {
    var request = new XMLHttpRequest();
    request.open('POST', 'https://private-anon-535ecb43fa-trakt.apiary-mock.com/oauth/device/token');
    request.setRequestHeader('Content-Type', 'application/json');
    request.onreadystatechange = function () {
        if (this.readyState === 4) {
            switch (this.status) {
                case 200:
                    // Get Response and put in array
                    var response = JSON.parse(this.responseText);
                    // Save Access Token and Refresh Token to storage
                    chrome.storage.local.set({"access_token": response.access_token, "refresh_token": response.refresh_token});
                    // Set success notification
                    notify({"title": "Trakt for IMDb Authentication Success", "content": "Awesome, you've authenticated your Trakt account! Enjoy using Trakt for IMDb."});
                    // Set successComp to 1 so clearTimeout doesn't trigger
                    successComp = 1;
                    break;
                case 400:
                    // Request still pending
                    setTimeout(pollAccessToken.bind(null, device_code).bind(null, interval), interval * 1000);
                    break;
                case 404:
                    // Display Request not found/invalid error
                    notify({"title": "Trakt for IMDb Authentication Error", "content": "Whoops, looks like there was an error authenticating your Trakt account. Please try again."});
                    break;
                case 409:
                    // Code already used
                    notify({"title": "Trakt for IMDb already approved", "content": "Whoops, looks like you've already approved this code. If you continue to have issues, please try again."});
                    break;
                case 410:
                    // Code Expired
                    notify({"title": "Trakt for IMDb Authentication Token Error", "content": "Whoops, looks like your token expired and there was an error authenticating your Trakt account. Please try again"});
                    break;
                case 418:
                    // Code Denied
                    notify({"title": "Trakt for IMDb Authentication Denied", "content": "Oh no, it looks like you denied us access to your account! If you didn't mean to do this, please try again."});
                    break;
                case 429:
                    // Slow down polling
                    setTimeout(pollAccessToken.bind(null, device_code).bind(null, interval), interval * 1100);
                    break;
                default:
                    // Request still pending
                    setTimeout(pollAccessToken.bind(null, device_code).bind(null, interval), interval * 1000);
                    break;
            }
        }
    };
    var body = {
        'code': device_code,
        'client_id': APP_KEY,
        'client_secret': APP_SEC
    };
    request.send(JSON.stringify(body));
}
// Stop Polling Function
function stopPolling() {
    if (successComp === 0) {
        // Response polling expired
        clearTimeout(pollAccess);
        notify({"title": "Trakt for IMDb Authentication Timed Out", "content": "Whoops, looks like you took too long in authenticating your Trakt account. Please try again."});
    }
}
// Assign functions based on type
chrome.runtime.onMessage.addListener(function(data) {
    switch (data.type) {
        case "notification":
            notify(data);
            break;
        case "pollRequest":
            var pollAccess = pollAccessToken(data.device_code, data.interval);
            setTimeout(stopPolling, data.expires_in * 1000);
            break;
    }
});
dpDesignz
  • 1,909
  • 10
  • 34
  • 70
  • `If anyone can tell me why the following WOULDN'T work how I want it to` - does it work how you want it to? If it does, I can't see why it wouldn't, if it doesn't, then you need to change the code so that it does – Jaromanda X Aug 13 '16 at 02:48
  • So far from what I can tell it does. But I'm not sure if I'm doing this the best way possible :) – dpDesignz Aug 13 '16 at 03:41