I'm trying to use a CloudFlare worker to manage my backend ESI fragments but i get an error:
Uncaught (in promise) TypeError: Body has already been used. It can only be used once. Use tee() first if you need to read it twice.
Uncaught (in response) TypeError: Body has already been used. It can only be used once. Use tee() first if you need to read it twice.
I don't find where the body has already been used
The process is:
get a response with the parts
Transform the body by replacing parts fragments with sub Backend calls (streamTransformBody function)
return the response
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
});
const esiHeaders = {
"user-agent": "cloudflare"
}
async function handleRequest(request) {
// get cookies from the request
if(cookie = request.headers.get("Cookie")) {
esiHeaders["Cookie"] = cookie
console.log(cookie)
}
// Clone the request so that it's no longer immutable
newRequest = new Request(request)
// remove cookie from request
newRequest.headers.delete('Cookie')
// Add header to get <esi>
newRequest.headers.set("Surrogate-Capability", "abc=ESI/1.0")
console.log(newRequest.url);
const response = await fetch(newRequest);
let contentType = response.headers.get('content-type')
if (!contentType || !contentType.startsWith("text/")) {
return response
}
// Clone the response so that it's no longer immutable
const newResponse = new Response(response.body, response);
let { readable, writable } = new TransformStream()
streamTransformBody(newResponse.body, writable)
newResponse.headers.append('x-workers-hello', 'Hello from
Cloudflare Workers');
return newResponse;
}
async function streamTransformBody(readable, writable) {
const startTag = "<".charCodeAt(0);
const endTag = ">".charCodeAt(0);
let reader = readable.getReader();
let writer = writable.getWriter();
let templateChunks = null;
while (true) {
let { done, value } = await reader.read();
if (done) break;
while (value.byteLength > 0) {
if (templateChunks) {
let end = value.indexOf(endTag);
if (end === -1) {
templateChunks.push(value);
break;
} else {
templateChunks.push(value.subarray(0, end));
await writer.write(await translate(templateChunks));
templateChunks = null;
value = value.subarray(end + 1);
}
}
let start = value.indexOf(startTag);
if (start === -1) {
await writer.write(value);
break;
} else {
await writer.write(value.subarray(0, start));
value = value.subarray(start + 1);
templateChunks = [];
}
}
}
await writer.close();
}
async function translate(chunks) {
const decoder = new TextDecoder();
let templateKey = chunks.reduce(
(accumulator, chunk) =>
accumulator + decoder.decode(chunk, { stream: true }),
""
);
templateKey += decoder.decode();
return handleTemplate(new TextEncoder(), templateKey);
}
async function handleTemplate(encoder, templateKey) {
const linkRegex = /(esi:include.*src="(.*?)".*\/)/gm
let result = linkRegex.exec(templateKey);
let esi
if (!result) {
return encoder.encode(`<${templateKey}>`);
}
if (result[2]) {
esi = await subRequests(result[2]);
}
return encoder.encode(
`${esi}`
);
}
async function subRequests(target){
target = esiHost + target
const init = {
method: 'GET',
headers: esiHeaders
}
let response = await fetch(target, init)
if (!response.ok) {
return ''
}
let text = await response.text()
return '<!--esi-->' + text + '<!--/esi-->'
}
Ok so my bot got rebuilt with a somewhat different code.
I'm using a somewhat more simplified fs command and events handler. My command works as intended.
But I'm wanting to add the amount pruned into the fields for the richEmbed and it keeps erroring out.
Here is my purge.js file
const Discord = require('discord.js')
module.exports = {
name: 'purge',
description: 'Purge up to 99 messages.',
execute(message, args) {
console.log("purging messages")
const embed = new Discord.RichEmbed()
.setTitle("Success")
.setColor(0x00AE86)
.setFooter("Guardian", "https://raw.githubusercontent.com/phantomdev-github/Resources/master/Discord%20Bots/Guardian/src/avatar.png")
.setThumbnail("https://raw.githubusercontent.com/phantomdev-github/Resources/master/Discord%20Bots/Guardian/src/avatar.png")
.setTimestamp()
.setURL("https://github.com/phantomdev-github/Resources/tree/master/Discord%20Bots/Guardian")
.addField("Bot Messages Purged", "missing code here", false)
.addField("User Pins Purged", "missing code here", false)
.addField("User Messages Purged", "missing code here", false)
.addField("Total Messages Purged", "missing code here", false)
message.channel.send({ embed });
const amount = parseInt(args[0]) + 1;
if (isNaN(amount)) {
return message.reply('that doesn\'t seem to be a valid number.');
} else if (amount <= 1 || amount > 100) {
return message.reply('you need to input a number between 1 and 99.');
}
message.channel.bulkDelete(amount, true).catch(err => {
console.error(err);
message.channel.send('there was an error trying to prune messages in this channel!');
});
},
};
If it helps this i my index.js
const fs = require('fs');
const Discord = require('discord.js');
const client = new Discord.Client();
const { token } = require('./token.json');
client.commands = new Discord.Collection();
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
client.commands.set(command.name, command);
console.log(file,command)
}
fs.readdir('./events/', (err, files) => {
if (err) return console.error(err);
files.forEach(file => {
if(!file.endsWith('.js')) return;
const eventFunction = require(`./events/${file}`);
console.log(eventFunction)
eventFunction.execute(client)
});
});
client.login(token);
and this is my message.js
const { prefix } = require('./prefix.json');
module.exports = {
name: 'message',
description: '',
execute:function(client) {
client.on('message',message => {
if (!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).split(/ +/);
const command = args.shift().toLowerCase();
if (!client.commands.has(command)) return;
try {
client.commands.get(command).execute(message, args);
} catch (error) {
console.error(error);
message.reply('there was an error trying to execute that command!');
}
})
}};
Basically I'm trying to figure out what to place into the "missing code here" sections. Also any way to lock it to people with Administrator permissions only would be useful as well. I attempted that but it failed to work with the embed.
If I understand you right you want to know how to get the amount of the purged pins, bot msgs and user msgs. For this you need to put your embed after you deleted the messages.
purge.js
const Discord = require('discord.js')
module.exports = {
name: 'purge',
description: 'Purge up to 99 messages.',
execute(message, args) {
console.log("purging messages")
const amount = parseInt(args[0]) + 1;
if (isNaN(amount)) {
return message.reply('that doesn\'t seem to be a valid number.');
} else if (amount <= 1 || amount > 100) {
return message.reply('you need to input a number between 1 and 99.');
}
message.channel.bulkDelete(amount, true).then(deletedMessages => {
// Filter the deleted messages with .filter()
var botMessages = deletedMessages.filter(m => m.author.bot);
var userPins = deletedMessages.filter(m => m.pinned);
var userMessages = deletedMessages.filter(m => !m.author.bot);
const embed = new Discord.RichEmbed()
.setTitle("Success")
.setColor(0x00AE86)
.setFooter("Guardian", "https://raw.githubusercontent.com/phantomdev-github/Resources/master/Discord%20Bots/Guardian/src/avatar.png")
.setThumbnail("https://raw.githubusercontent.com/phantomdev-github/Resources/master/Discord%20Bots/Guardian/src/avatar.png")
.setTimestamp()
.setURL("https://github.com/phantomdev-github/Resources/tree/master/Discord%20Bots/Guardian")
.addField("Bot Messages Purged", botMessages.size, false)
.addField("User Pins Purged", userPins.size, false)
.addField("User Messages Purged", userMessages.size, false)
.addField("Total Messages Purged", deletedMessages.size, false);
message.channel.send(embed);
}).catch(err => {
console.error(err);
message.channel.send('there was an error trying to prune messages in this channel!');
});
},
};
I use angular 8 and i want to test my component with FileReader.
I can not test a FileReader in my processFile function.
Maybe my work is badly written? Can you help me please to understand.
IF I understand correctly, I have to test a class (Filereader) in a process function
my component
processFile(imageInput: any) {
const file: File = imageInput.files[0];
const reader = new FileReader();
let size: number = 2097152
if (file) {
if (file.size <= size) {
this.sharingDataService.setUploadIsOk(true)
reader.addEventListener('progress', (event:any) =>{
this.progressValue = this.progressBar(event)
if (event.lengthComputable) {
// console.log(event.loaded+ " / " + event.total)
}
})
reader.addEventListener('loadstart', (event:any) =>{
this.progressValue =0;
this.textDuringUploadBefore = "No"
this.textDuringUploadAfter = ''
// console.log('start');
})
reader.addEventListener('loadend', (event:any) =>{
// console.log('end');
})
reader.addEventListener('load', (event: any) => {
console.log(event);
this.selectedFile = new ImageSnippet(event.target.result, file);
this.fileName = this.selectedFile.file.name;
this.fileNameExt =this.fileName.split('.').pop();
this.displayAddPhoto = false;
this.selectedFile.status = 'ok';
this.getLoadCallBack(file)
// this.ng2ImgMax.resizeImage(file, 900,600).subscribe(
// result =>{
// // console.log('compress', );
// this.textDuringUploadAfter= "Yes!!!"
// this.textDuringUploadBefore= ''
// this.fileForm.patchValue({
// image: new File([result], result.name)
// });
// this.imageIsLoad = true
// this.sharingDataService.setUploadIsOk(false)
// }
// )
// this.imageOutput.emit(this.fileForm)
});
reader.readAsDataURL(file);
} else {
const msg ="This picture is too big."
+ '<br/>' + "Please upload an image of less than 2MB."
// this.sharedFunctionService.openDialogAlert(msg, 'home')
this.errorService.openDialogError(msg)
this.imageIsLoad = false
this.sharingDataService.setUploadIsOk(false)
}
}
}
getLoadCallBack(file:File){
this.ng2ImgMax.resizeImage(file, 900,600).subscribe(
result =>{
// console.log('compress', );
this.textDuringUploadAfter= "Yes"
this.textDuringUploadBefore= ''
this.fileForm.patchValue({
image: new File([result], result.name)
});
console.log(this.fileForm);
this.imageIsLoad = true
this.sharingDataService.setUploadIsOk(false)
}
)
this.imageOutput.emit(this.fileForm)
}
my spec.ts
it('processFile', () => {
// const mockEvt = { target: { files: [fileInput] } };
// const mockReader: FileReader = jasmine.createSpyObj('FileReader', ['readAsDataURL', 'onload']);
// spyOn(window as any, 'FileReader').and.returnValue(mockReader);
// spyOn(component, 'getLoadCallBack').and.callThrough();
const file = new File([''], 'test-file.jpg', { lastModified: null, type: 'image/jpeg' });
const fileInput = { files: [file] };
const eventListener = jasmine.createSpy();
spyOn(window as any, "FileReader").and.returnValue({
addEventListener: eventListener
})
component.processFile(fileInput);
i have got an error
TypeError: reader.readAsDataURL is not a function
how to test my processFile function?
I trie many way but no sucess
I am using the react-admin package and it has come to my attention that I needed to rewrite my functioning data provider to merge two different api request results into one array of data before passing the data to the resource component that would display it. After my rewrite I console log the data being returned and it is correct, but no matter what I have tried I always get the "Cannot read property 'hasOwnProperty' of undefined" error before my console.log of the data, and then I get the same error message a few seconds later, and nothing gets displayed.
in dataprovider.js (displays the data in the resource component)
export default (type, resource, params) => {
var apiUrl = `https://request1url.com/api`;
let query = '';
let url = '';
const options = {
headers : new Headers({
Accept: 'application/json',
}),
};
switch (type) {
case GET_LIST: {
if(resource === 'errors'){
query = '/query/errors';
}
if(resource === 'people'){
query = '/query/users';
}
url = `${apiUrl}${query}`;
break;
}
default:
throw new Error(`Unsupported Data Provider request type ${type}`);
}
return fetch(url, options)
.then(res => {
return res.json()
})
.then(json => {
var data = [];
var result = json.data.result;
for(var i = 0; i < result.length; i++){
result[i].id = i
data.push(result[i])
}
}
console.log(data)
switch (type) {
case GET_LIST:
return {
data: data,
total: data.length
}
case GET_ONE:
return {
data: data,
}
default:
return { data: data};
}
});
};
in NEWdataprovider.js (rewrite)
export default (type, resource, params) => {
const apiRequests = ['https://request1url.com/api','https://request2url.com/api'];
let query = '';
const options = {
headers : new Headers({
Accept: 'application/json',
}),
};
switch (type) {
case GET_LIST: {
if(resource === 'errors'){
query = '/query/errors';
}
if(resource === 'people'){
query = '/query/users';
}
break;
}
default:
throw new Error(`Unsupported Data Provider request type ${type}`);
}
var req1 = fetch(`${apiRequests[0]}${query}`, options).then(function(response){
return response.json()
});
var req2 = fetch(`${apiRequests[1]}${query}`, options).then(function(response){
return response.json()
});
Promise.all([req1,req2]).then(function(values){
var data = [];
var result1 = values[0].data.result;
var result2 = values[1].data.result;
for(var i = 0; i < result1.length; i++){
result1[i].id = i
data.push(result1[i])
}
for(var j = 0; j < result2.length; j++){
result2[j].id = j
data.push(result2[j])
}
console.log(data)
switch (type) {
case GET_LIST:
return {
data: data,
total: data.length
}
default:
return { data: data};
}
});
};
in App.js
<Admin dataProvider={dataProvider}>
<Resource name="errors" list={errors} />
<Resource name="people" list={people} />
</Admin>
);
in the console.log the data logged is the correct format, and data to be displayed, but with the original dataprovider it displayed the list of items, and the new dataprovider returns the error message "Cannot read property 'hasOwnProperty' of undefined"
Your dataprovider needs to return a promise.
Per the docs...
/**
* Query a data provider and return a promise for a response
*
* #example
* dataProvider(GET_ONE, 'posts', { id: 123 })
* => Promise.resolve({ data: { id: 123, title: "hello, world" } })
*
* #param {string} type Request type, e.g GET_LIST
* #param {string} resource Resource name, e.g. "posts"
* #param {Object} payload Request parameters. Depends on the action type
* #returns {Promise} the Promise for a response
*/
const dataProvider = (type, resource, params) => new Promise();
Try return Promise.all() instead.
export default (type, resource, params) => {
const apiRequests = ['https://request1url.com/api','https://request2url.com/api'];
let query = '';
const options = {
headers : new Headers({
Accept: 'application/json',
}),
};
switch (type) {
case GET_LIST: {
if(resource === 'errors'){
query = '/query/errors';
}
if(resource === 'people'){
query = '/query/users';
}
break;
}
default:
throw new Error(`Unsupported Data Provider request type ${type}`);
}
var req1 = fetch(`${apiRequests[0]}${query}`, options).then(function(response){
return response.json()
});
var req2 = fetch(`${apiRequests[1]}${query}`, options).then(function(response){
return response.json()
});
return Promise.all([req1,req2]).then(function(values){
var data = [];
var result1 = values[0].data.result;
var result2 = values[1].data.result;
for(var i = 0; i < result1.length; i++){
result1[i].id = i
data.push(result1[i])
}
for(var j = 0; j < result2.length; j++){
result2[j].id = j
data.push(result2[j])
}
console.log(data)
switch (type) {
case GET_LIST:
return {
data: data,
total: data.length
}
default:
return { data: data};
}
});
};
I'd like to send base64 image as an attachment to a trello card through the API
POST /1/cards/[card id or shortlink]/attachments
There's a file field but it does not specify how should look the data there.
Refs: https://developers.trello.com/advanced-reference/card#post-1-cards-card-id-or-shortlink-attachments
Any idea?
Short answer: Trello's API only works to attach binary data via multipart/form-data. Examples below.
Long answer:
The Trello API to add attachments and images is frustratingly under-documented. They do have a coffeescript Client.js for those of us using Javascript to simplify all the basic operations: https://trello.com/1/client.coffee
Using the vanilla Client.js file I have been able to attach links and text files. While the CURL example shows pulling a binary file in from a hard drive, that doesn't work for those of us completely on a server or client where we don't have permissions to create a file.
From a LOT of trial and error, I've determined all binary data (images, documents, etc.) must be attached using multipart/form-data. Here is a jQuery snippet that will take a URL to an item, get it into memory, and then send it to Trello.
var opts = {'key': 'YOUR TRELLO KEY', 'token': 'YOUR TRELLO TOKEN'};
var xhr = new XMLHttpRequest();
xhr.open('get', params); // params is a URL to a file to grab
xhr.responseType = 'blob'; // Use blob to get the file's mimetype
xhr.onload = function() {
var fileReader = new FileReader();
fileReader.onload = function() {
var filename = (params.split('/').pop().split('#')[0].split('?')[0]) || params || '?'; // Removes # or ? after filename
var file = new File([this.result], filename);
var form = new FormData(); // this is the formdata Trello needs
form.append("file", file);
$.each(['key', 'token'], function(iter, item) {
form.append(item, opts.data[item] || 'ERROR! Missing "' + item + '"');
});
$.extend(opts, {
method: "POST",
data: form,
cache: false,
contentType: false,
processData: false
});
return $.ajax(opts);
};
fileReader.readAsArrayBuffer(xhr.response); // Use filereader on blob to get content
};
xhr.send();
I have submitted a new coffeescript for Trello developer support to consider uploading to replace Client.js. It adds a "Trello.upload(url)" that does this work.
I've also attached here for convenience in JS form.
// Generated by CoffeeScript 1.12.4
(function() {
var opts={"version":1,"apiEndpoint":"https://api.trello.com","authEndpoint":"https://trello.com"};
var deferred, isFunction, isReady, ready, waitUntil, wrapper,
slice = [].slice;
wrapper = function(window, jQuery, opts) {
var $, Trello, apiEndpoint, authEndpoint, authorizeURL, baseURL, collection, fn, fn1, i, intentEndpoint, j, key, len, len1, localStorage, location, parseRestArgs, readStorage, ref, ref1, storagePrefix, token, type, version, writeStorage;
$ = jQuery;
key = opts.key, token = opts.token, apiEndpoint = opts.apiEndpoint, authEndpoint = opts.authEndpoint, intentEndpoint = opts.intentEndpoint, version = opts.version;
baseURL = apiEndpoint + "/" + version + "/";
location = window.location;
Trello = {
version: function() {
return version;
},
key: function() {
return key;
},
setKey: function(newKey) {
key = newKey;
},
token: function() {
return token;
},
setToken: function(newToken) {
token = newToken;
},
rest: function() {
var args, error, method, params, path, ref, success;
method = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
ref = parseRestArgs(args), path = ref[0], params = ref[1], success = ref[2], error = ref[3];
opts = {
url: "" + baseURL + path,
type: method,
data: {},
dataType: "json",
success: success,
error: error
};
if (!$.support.cors) {
opts.dataType = "jsonp";
if (method !== "GET") {
opts.type = "GET";
$.extend(opts.data, {
_method: method
});
}
}
if (key) {
opts.data.key = key;
}
if (token) {
opts.data.token = token;
}
if (params != null) {
$.extend(opts.data, params);
}
if (method === 'UPLOAD' && typeof (params) === "string" && params.length > 5) {
var xhr = new XMLHttpRequest();
xhr.open('get', params);
xhr.responseType = 'blob'; // Use blob to get the mimetype
xhr.onload = function() {
var fileReader = new FileReader();
fileReader.onload = function() {
var filename = (params.split('/').pop().split('#')[0].split('?')[0]) || params || '?'; // Removes # or ? after filename
var file = new File([this.result], filename);
var form = new FormData();
form.append("file", file);
$.each(['key', 'token'], function(iter, item) {
form.append(item, opts.data[item] || 'ERROR! Missing "' + item + '"');
});
$.extend(opts, {
method: "POST",
data: form,
cache: false,
contentType: false,
processData: false
});
return $.ajax(opts);
};
fileReader.readAsArrayBuffer(xhr.response); // Use filereader on blob to get content
};
xhr.send();
} else {
return $.ajax(opts);
}
},
authorized: function() {
return token != null;
},
deauthorize: function() {
token = null;
writeStorage("token", token);
},
authorize: function(userOpts) {
var k, persistToken, ref, regexToken, scope, v;
opts = $.extend(true, {
type: "redirect",
persist: true,
interactive: true,
scope: {
read: true,
write: false,
account: false
},
expiration: "30days"
}, userOpts);
regexToken = /[&#]?token=([0-9a-f]{64})/;
persistToken = function() {
if (opts.persist && (token != null)) {
return writeStorage("token", token);
}
};
if (opts.persist) {
if (token == null) {
token = readStorage("token");
}
}
if (token == null) {
token = (ref = regexToken.exec(location.hash)) != null ? ref[1] : void 0;
}
if (this.authorized()) {
persistToken();
location.hash = location.hash.replace(regexToken, "");
return typeof opts.success === "function" ? opts.success() : void 0;
}
if (!opts.interactive) {
return typeof opts.error === "function" ? opts.error() : void 0;
}
scope = ((function() {
var ref1, results;
ref1 = opts.scope;
results = [];
for (k in ref1) {
v = ref1[k];
if (v) {
results.push(k);
}
}
return results;
})()).join(",");
switch (opts.type) {
case "popup":
(function() {
var authWindow, height, left, origin, receiveMessage, ref1, top, width;
waitUntil("authorized", (function(_this) {
return function(isAuthorized) {
if (isAuthorized) {
persistToken();
return typeof opts.success === "function" ? opts.success() : void 0;
} else {
return typeof opts.error === "function" ? opts.error() : void 0;
}
};
})(this));
width = 420;
height = 470;
left = window.screenX + (window.innerWidth - width) / 2;
top = window.screenY + (window.innerHeight - height) / 2;
origin = (ref1 = /^[a-z]+:\/\/[^\/]*/.exec(location)) != null ? ref1[0] : void 0;
authWindow = window.open(authorizeURL({
return_url: origin,
callback_method: "postMessage",
scope: scope,
expiration: opts.expiration,
name: opts.name
}), "trello", "width=" + width + ",height=" + height + ",left=" + left + ",top=" + top);
receiveMessage = function(event) {
var ref2;
if (event.origin !== authEndpoint || event.source !== authWindow) {
return;
}
if ((ref2 = event.source) != null) {
ref2.close();
}
if ((event.data != null) && /[0-9a-f]{64}/.test(event.data)) {
token = event.data;
} else {
token = null;
}
if (typeof window.removeEventListener === "function") {
window.removeEventListener("message", receiveMessage, false);
}
isReady("authorized", Trello.authorized());
};
return typeof window.addEventListener === "function" ? window.addEventListener("message", receiveMessage, false) : void 0;
})();
break;
default:
window.location = authorizeURL({
redirect_uri: location.href,
callback_method: "fragment",
scope: scope,
expiration: opts.expiration,
name: opts.name
});
}
},
addCard: function(options, next) {
var baseArgs, getCard;
baseArgs = {
mode: 'popup',
source: key || window.location.host
};
getCard = function(callback) {
var height, left, returnUrl, top, width;
returnUrl = function(e) {
var data;
window.removeEventListener('message', returnUrl);
try {
data = JSON.parse(e.data);
if (data.success) {
return callback(null, data.card);
} else {
return callback(new Error(data.error));
}
} catch (error1) {}
};
if (typeof window.addEventListener === "function") {
window.addEventListener('message', returnUrl, false);
}
width = 500;
height = 600;
left = window.screenX + (window.outerWidth - width) / 2;
top = window.screenY + (window.outerHeight - height) / 2;
return window.open(intentEndpoint + "/add-card?" + $.param($.extend(baseArgs, options)), "trello", "width=" + width + ",height=" + height + ",left=" + left + ",top=" + top);
};
if (next != null) {
return getCard(next);
} else if (window.Promise) {
return new Promise(function(resolve, reject) {
return getCard(function(err, card) {
if (err) {
return reject(err);
} else {
return resolve(card);
}
});
});
} else {
return getCard(function() {});
}
}
};
ref = ["GET", "PUT", "POST", "DELETE", "UPLOAD"];
fn = function(type) {
return Trello[type.toLowerCase()] = function() {
return this.rest.apply(this, [type].concat(slice.call(arguments)));
};
};
for (i = 0, len = ref.length; i < len; i++) {
type = ref[i];
fn(type);
}
Trello.del = Trello["delete"];
ref1 = ["actions", "cards", "checklists", "boards", "lists", "members", "organizations", "lists"];
fn1 = function(collection) {
return Trello[collection] = {
get: function(id, params, success, error) {
return Trello.get(collection + "/" + id, params, success, error);
}
};
};
for (j = 0, len1 = ref1.length; j < len1; j++) {
collection = ref1[j];
fn1(collection);
}
window.Trello = Trello;
authorizeURL = function(args) {
var baseArgs;
baseArgs = {
response_type: "token",
key: key
};
return authEndpoint + "/" + version + "/authorize?" + $.param($.extend(baseArgs, args));
};
parseRestArgs = function(arg) {
var error, params, path, success;
path = arg[0], params = arg[1], success = arg[2], error = arg[3];
if (isFunction(params)) {
error = success;
success = params;
params = {};
}
path = path.replace(/^\/*/, "");
return [path, params, success, error];
};
localStorage = window.localStorage;
if (localStorage != null) {
storagePrefix = "trello_";
readStorage = function(key) {
return localStorage[storagePrefix + key];
};
writeStorage = function(key, value) {
if (value === null) {
return delete localStorage[storagePrefix + key];
} else {
return localStorage[storagePrefix + key] = value;
}
};
} else {
readStorage = writeStorage = function() {};
}
};
deferred = {};
ready = {};
waitUntil = function(name, fx) {
if (ready[name] != null) {
return fx(ready[name]);
} else {
return (deferred[name] != null ? deferred[name] : deferred[name] = []).push(fx);
}
};
isReady = function(name, value) {
var fx, fxs, i, len;
ready[name] = value;
if (deferred[name]) {
fxs = deferred[name];
delete deferred[name];
for (i = 0, len = fxs.length; i < len; i++) {
fx = fxs[i];
fx(value);
}
}
};
isFunction = function(val) {
return typeof val === "function";
};
wrapper(window, jQuery, opts);
}).call(this);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>