SE's Javascript SDK assumes that you have your own domain and that everything is same-domain. The SDK has code like:
// proxyUrl must be under the currently hosting domain
if (proxyUrl.toLowerCase().indexOf(domain) !== 0) {
throw 'channelUrl must be under the current domain';
}
in its source.
In a nutshell:
channelUrl
must be on the same domain as the calling page. Hard to do if you don't own that domain. :)
- Your app's
OAuth Domain
setting must be the same domain as channelUrl
. Again difficult for a userscript, and impossible if the userscript runs on several domains -- like a general purpose SE script must.
- Alternatively, the
OAuth Domain
can be stackexchange.com
(more on this below), but this conflicts with item 1 of this list.
Solutions:
Either:
- Buy and operate your own domain, allowing you to use the, limited usefulness, "Javascript SDK".
- Or trust some third party with your (and your user's) sacred OAuth bits. I'm sure ArtOfCode is a standup guy, but what happens when his server is hacked or the wife wins it all in a divorce, etc.? :P
- Or Roll your own OAuth code. A sample and framework follows:
A basic framework for cross-domain API authentication in a userscript:
Setup:
A sample script with the framework:
- WARNING: The following works but has not yet been tested as much as I prefer.
- The script will run as-is, but you should change the first three
const
s to match your own app.
- The script operates in a standard-ish parent/child configuration.
- See code comments for more.
// ==UserScript==
// @name StackExchange, API authenticate framework
// These are the target page matches. Start with the baseline Stack Exchange set. Refine to taste.
// @match *://*.askubuntu.com/*
// @match *://*.serverfault.com/*
// @match *://*.stackapps.com/*
// @match *://*.stackexchange.com/*
// @match *://*.stackoverflow.com/*
// @match *://*.superuser.com/*
// The following match is required for authentication to work.
// @match https://stackexchange.com/oauth/login_success*
// Baseline Stack Exchange excludes.
// @exclude *://api.stackexchange.com/*
// @exclude *://blog.stackexchange.com/*
// @exclude *://blog.stackoverflow.com/*
// @exclude *://chat.stackexchange.com/*
// @exclude *://chat.stackoverflow.com/*
// @exclude *://data.stackexchange.com/*
// @exclude *://elections.stackexchange.com/*
// @exclude *://openid.stackexchange.com/*
// @exclude *://*/review
// @require http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @reference https://meta.stackexchange.com/a/293498/148310
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
/*--- This script operates in one of two modes. Either on a main (target) page, or in an API Oauth dialog if required.
*/
const apiID = 1515; //-- Change this to your app's
const apikey = "dp55hR5Wr9UhOJReA6F2gg(("; //-- Change this to your app's
const authScope = "read_inbox"; //-- Change this to your needs
const apiBaseURL = "https://api.stackexchange.com/2.2";
var accessToken = null;
function scriptMain () {
//--- Place the payload code here. For example:
//-- Use the /inbox route:
var reqURL = apiBaseURL + `/inbox?filter=!6LdGvjJhl2HHw&key=${apikey}&access_token=${accessToken}`;
$.getJSON (reqURL, parseMeInbox).fail ( function (jqXHR, textStatus) {
console.error ("API error: ", textStatus);
console.error ("Detail: ", jqXHR.responseText);
GM_setValue ("APIToken", "");
} );
}
function parseMeInbox (zData) {
if (zData.items && zData.items.length) {
console.log ("Your last message was: ", zData.items[0].body);
}
else {
console.log ("No inbox messages found for you.");
}
//-- Always check for (and ideally handle) these errors:
if (zData.backoff)
console.error (`API sent backoff warning, ${zData.backoff} seconds.`);
if (zData.quota_remaining < 20)
console.error (`API low quota alert. ${zData.quota_remaining} remaining.`);
if (zData.error_id) {
console.error ( `
Error ${zData.error_id}, ${zData.error_name}.
${zData.error_message}
` );
//-- On authentication type errors, attempt to reauthenticate.
if (zData.error_id == 403) {
GM_setValue ("APIToken", "");
authorizeAndThenRunMain ();
}
}
}
/****************************************************************************************
*** Authentication processing code follows. Should not need alteration.
*/
if (location.pathname == "/oauth/login_success") {
processOauthPopup ();
}
else {
authorizeAndThenRunMain ();
}
function authorizeAndThenRunMain () {
//-- Only run max 2 times, to prevent bad auth loops.
let self = authorizeAndThenRunMain;
self.runcnt = self.runcnt || 0;
if (self.runcnt++ > 1) return;
var authWindow = null;
accessToken = GM_getValue ("APIToken");
if (accessToken) {
/*-- Can check token to make sure it's still good.
See https://stackapps.com/a/7105/7653 and https://stackapps.com/a/4980/7653
BUT more quota efficient to make payload call and only reauthenticate when get 403 error.
*/
scriptMain ();
}
else {
authWindow = window.open (
`https://stackexchange.com/oauth/dialog?client_id=${apiID}&scope=${authScope}&redirect_uri=https://stackexchange.com/oauth/login_success`,
"GM to API Auth window",
"resizable,scrollbars,status,toolbar,dependent,width=660,height=480"
);
if (!authWindow) {
alert ("This script requires popups to be enabled.")
throw "This script requires popups to be enabled.";
}
window.addEventListener ("message", receiveAPI_Token);
}
}
function receiveAPI_Token (zEvent) {
if (zEvent.origin != "https://stackexchange.com") return;
//#access_token=jvH3nWQGH*********59*w))&expires=86399
var aTknMtch = zEvent.data.match (/access_token=([^&]+)&?/);
if (aTknMtch && aTknMtch.length > 1) {
accessToken = aTknMtch[1];
GM_setValue ("APIToken", accessToken);
scriptMain ();
}
else {
console.error ("Unable to authenticate! Error: ", zEvent.data);
}
}
function processOauthPopup () {
if (window.opener) {
if (location.hash) {
window.opener.postMessage (location.hash, "*");
window.close ();
}
else {
window.opener.postMessage ('Unknown error with Oauth Dialog.', "*");
console.error ('Unknown error with Oauth Dialog.');
}
}
}
SE.init
fails with "channelUrl must be under the current domain", and I don't even get as far as being able to attempt to authenticate.