diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/$disabled/$email/.gitignore (renamed from modules/email/.gitignore) | 0 | ||||
-rw-r--r-- | modules/$disabled/$email/email.js (renamed from modules/email/email.js) | 12 | ||||
-rw-r--r-- | modules/blog/blog.js | 2 | ||||
-rw-r--r-- | modules/changes/changes.js | 2 | ||||
-rw-r--r-- | modules/nlen/English-Dutch-mistranslations.json | 8 | ||||
-rw-r--r-- | modules/proxy/proxy.js | 10 | ||||
-rw-r--r-- | modules/save/save.js | 2 | ||||
-rw-r--r-- | modules/statusbot/statusbot.js | 207 | ||||
-rw-r--r-- | modules/timetrack/timetrack.js | 2 | ||||
-rw-r--r-- | modules/timetrack2/timetrack2.js | 2 | ||||
-rw-r--r-- | modules/timetrack3/timetrack3.js | 2 | ||||
-rw-r--r-- | modules/todo/todo.js | 2 | ||||
-rw-r--r-- | modules/unicode/aliases.txt | 2 | ||||
-rw-r--r-- | modules/unicode/index.html | 17 | ||||
-rw-r--r-- | modules/up_log/.gitignore | 2 | ||||
-rw-r--r-- | modules/up_log/up_log.js | 29 | ||||
-rw-r--r-- | modules/zelfoverhoor/zelfoverhoor.js | 14 |
17 files changed, 247 insertions, 68 deletions
diff --git a/modules/email/.gitignore b/modules/$disabled/$email/.gitignore index df02057..df02057 100644 --- a/modules/email/.gitignore +++ b/modules/$disabled/$email/.gitignore diff --git a/modules/email/email.js b/modules/$disabled/$email/email.js index e4d342b..d7fe171 100644 --- a/modules/email/email.js +++ b/modules/$disabled/$email/email.js @@ -41,10 +41,16 @@ function sendEmail(recip, text) { } module.exports = function(app, io, moddir) { - var allowedRecipients = - fs.readFileSync(moddir + "/allowed_recipients.txt").toString().trim().split("\n"); + var allowedRecipients, password; - var password = fs.readFileSync(moddir + "/password.txt").toString().trim(); + try { + allowedRecipients = + fs.readFileSync(moddir + "/allowed_recipients.txt").toString().trim().split("\n"); + password = fs.readFileSync(moddir + "/password.txt").toString().trim(); + } catch (e) { + console.error(e); + return false; + } app.post("/email", bodyParser.json(), function(req, res) { var body = req.body; diff --git a/modules/blog/blog.js b/modules/blog/blog.js index 3985fdb..4843d4b 100644 --- a/modules/blog/blog.js +++ b/modules/blog/blog.js @@ -112,7 +112,7 @@ module.exports = (app, io, _moddir) => { next(); }); - app.get("/blog/*", (req, res) => { + app.get("/blog/*rest", (req, res) => { if (req.path.indexOf("/.") != -1) { res.sendStatus(404); return; diff --git a/modules/changes/changes.js b/modules/changes/changes.js index 0d8fa24..9fa1e44 100644 --- a/modules/changes/changes.js +++ b/modules/changes/changes.js @@ -320,7 +320,7 @@ function performCleanup(){ module.exports=function(app,io,_moddir){ moddir=_moddir; - app.all(["/changes","/changes/*"],cmn.authgen()); + app.all(["/changes","/changes/*rest"],cmn.authgen()); app.get("/changes",function(req,res){ res.sendFile(moddir+"/changes.html"); }); diff --git a/modules/nlen/English-Dutch-mistranslations.json b/modules/nlen/English-Dutch-mistranslations.json index c4f0d2f..9aa919c 100644 --- a/modules/nlen/English-Dutch-mistranslations.json +++ b/modules/nlen/English-Dutch-mistranslations.json @@ -19,7 +19,7 @@ {"nl":"zachte goederen","en":"software"}, {"nl":"harde goederen","en":"hardware"}, {"nl":"Hyper-Tekst Markeer-Op Taal","en":"HTML"}, -{"nl":"client","en":"cliënt"}, +{"nl":"klant","en":"client"}, {"nl":"serveerder","en":"server"}, {"nl":"databasis","en":"database"}, {"nl":"voorhalen","en":"pre-fetch"}, @@ -29,8 +29,8 @@ {"nl":"Toepassing-Programmeer-Tussengezicht","en":"API"}, {"nl":"Principes van Meteoor\nData op het Snoer. Meteor stuurt geen HTMOT (Hyper Tekst Markeer-Op Taal) over het netwerk. De server stuurt gegevens en laat de cliënt deze weergeven.\nÉén Taal. Meteoor laat je beide de cliënt- en de serveerderdelen van je toepassing in JavaScript schrijven.\nDatabasis Overal. Je kan dezelfde methodes gebruiken om je databasis te bereiken van de cliënt of de serveerder.\nWachttijdcompensatie. Op de cliënt, Meteoor voorhaalt gegevens op en simuleert modellen om het te maken lijken alsof de serveerder methode roepingen instantaan terugkomen.\nVolle-stapel reagerendheid. In Meteoor, werkelijktijd is de standaard. Alle lagen, van databasis tot blauwdruk, vernieuwen zichzelf automatisch wanneer benodigd.\nOmarm het ecosysteem. Meteoor is open bron en integreert met existerende open bron werktuigen en raamwerken.\nEenvoudigheid Evenaart Productiviteit. De beste weg om iets simpel te laten lijken is om het werkelijk simpel te hebben zijn. Meteoor's hoofdfunctionaliteit heeft schone, klassiek-mooie TPT's (Toepassing Programmeer Tussengezichten).","en":"Principles of Meteor\nData on the Wire. Meteor doesn't send HTML over the network. The server sends data and lets the client render it.\nOne Language. Meteor lets you write both the client and the server parts of your application in JavaScript.\nDatabase Everywhere. You can use the same methods to access your database from the client or the server.\nLatency Compensation. On the client, Meteor prefetches data and simulates models to make it look like server method calls return instantly.\nFull Stack Reactivity. In Meteor, realtime is the default. All layers, from database to template, update themselves automatically when necessary.\nEmbrace the Ecosystem. Meteor is open source and integrates with existing open source tools and frameworks.\nSimplicity Equals Productivity. The best way to make something seem simple is to have it actually be simple. Meteor's main functionality has clean, classically beautiful APIs."}, {"nl":"opladen","en":"to upload"}, -{"nl":"Breedte-eerst zoekopdracht","en":"Breadth-first search"}, -{"nl":"zwevende-punt rekenen","en":"floating-point arithmetic"}, -{"nl":"multikernprocessor","en":"multi-core processor"}, +{"nl":"Breedte-eerstzoektocht","en":"Breadth-first search"}, +{"nl":"zwevende-komma rekenen","en":"floating-point arithmetic"}, +{"nl":"meerkernverwerker","en":"multi-core processor"}, {"nl":"automatisch differentiëren in omgekeerde modus","en":"reverse-mode automatic differentiation"} ] diff --git a/modules/proxy/proxy.js b/modules/proxy/proxy.js index 49caf54..3ebe236 100644 --- a/modules/proxy/proxy.js +++ b/modules/proxy/proxy.js @@ -29,11 +29,17 @@ function objectFromList(l){ } module.exports=function(app,io,moddir){ - var iddict=objectFromList( + var iddict; + try { + iddict=objectFromList( fs.readFileSync(moddir+"/idfile.txt").toString() .split("\n").map(function(r){return r.split(" ");})); + } catch (e) { + console.error(e); + return false; + } - app.all("/proxy/:id/*",function(req,res){ + app.all("/proxy/:id/*rest",function(req,res){ var id=req.params.id; var path="/"+req.path.split("/").slice(3).join("/"); if(iddict[id]){ diff --git a/modules/save/save.js b/modules/save/save.js index 9873a52..ae1cfaf 100644 --- a/modules/save/save.js +++ b/modules/save/save.js @@ -46,7 +46,7 @@ module.exports=function(app,io,_moddir){ }); }); - app.all(["/save/read","/save/read/*"],cmn.authgen()); + app.all(["/save/read","/save/read/*rest"],cmn.authgen()); app.get("/save/read",function(req,res){ res.sendFile(moddir+"/read.html"); diff --git a/modules/statusbot/statusbot.js b/modules/statusbot/statusbot.js index 0fd9ce5..94894a9 100644 --- a/modules/statusbot/statusbot.js +++ b/modules/statusbot/statusbot.js @@ -11,7 +11,7 @@ // POST /statusbot // { "sender": "who are you", "text": "content of the status message" } -// State in this file: the above matrix.json plus: +// State in this file (gstate): the above matrix.json plus: // { // config: the above matrix.json, // home_server: "tomsmeding.com", // nominal server in the user id @@ -46,6 +46,8 @@ if (!persistTokens) { persist.setItemSync("access_tokens", persistTokens); } +let gstate = null; + // cb: (status: int, body: string) => () function fetch(method, headers, hostname, path, data, cb) { const req = https.request({method, headers, hostname, path}, res => { @@ -58,6 +60,86 @@ function fetch(method, headers, hostname, path, data, cb) { req.end(); } +// ms: int (milliseconds) +// f: ((...Args) => ()) => () +// cb: (true, ...Args) => () | (false) => () +// Callback is invoked with 'false' if the timeout expired, or with 'true' if 'f' finished before the timeout. +function ptimeout(ms, f, cb) { + let cbinvoked = false; + + const tmout = setTimeout(() => { + if (cbinvoked) return; + cbinvoked = true; + cb(false); + }, ms); + + f((...args) => { + if (cbinvoked) return; + cbinvoked = true; + clearTimeout(tmout); + cb(true, ...args); + }); +} + +class PRatelimit { + constructor(mindelayms) { + this._mindelayms = mindelayms; + this._queue = []; // [(() => ()) => ()] + this._ready = true; // true: should run job immediately; false: should enqueue + } + + // f: (() => ()) => () + submit(f) { + if (this._ready) { + this._ready = false; + f(() => this.startWaiting()); + } else { + this._queue.push(f); + } + } + + startWaiting() { + setTimeout(() => { + if (this._queue.length > 0) { + const f = this._queue.shift(); + f(() => this.startWaiting()); + } else { + this._ready = true; + } + }, this._mindelayms); + } +} + +// f: ((true, ...Args) => () | (false) => ()) => () +// cb: (true, ...Args) => () | (false) => () +// The callback to f should be invoked with 'true' plus further arguments if it +// succeeded, and 'false' if it did not. +// Based on this info, if f failed, f is retried a few times. Once ntimes has +// been exhausted or a call succeeds, cb is invoked with respectively either +// (false) or (true, ...args). +function pretry(interval, multiplier, ntimes, f, cb) { + let tm = interval; + let ncalled = 0; + + function schedule() { + setTimeout(() => { + f((success, ...args) => { + if (success) { + cb(true, ...args); + return; + } + + ncalled++; + tm *= multiplier; + if (ncalled >= ntimes) cb(false); + else schedule(); + }); + }, tm); + } + + schedule(); +} + // cb: (state) => () function augmentConfig(config, cb) { const m = config.user_id.match(/^@[^:]+:(.*)$/); @@ -73,40 +155,60 @@ function augmentConfig(config, cb) { [access_token, req_counter] = persistTokens[pkey]; } - fetch("GET", {}, home_server, "/.well-known/matrix/server", "", (status, body) => { - if (status != 200) { - throw new Error(`statusbot: Failed getting https://${home_server}/.well-known/matrix/server`); - } - const mserver = JSON.parse(body)["m.server"]; - const m = mserver.match(/^([^:]*)(:443)?$/); - if (!m) { - throw new Error(`statusbot: Matrix server port not 443 (sorry): <${mserver}>`); + // wait a bit with requesting this in case the homeserver in question is the same server as us + pretry(500, 1.4, 10, + cb => { + console.log("statusbot: Attempting to get matrix server info"); + fetch("GET", {}, home_server, "/.well-known/matrix/server", "", (status, body) => { + if (status != 200) { + cb(false); // attempt failed (perhaps a later attempt will succeed) + return; + } + let mserver; + try { + mserver = JSON.parse(body)["m.server"]; + } catch (e) { + cb(false); // invalid JSON response, try again later + return; + } + const m = mserver.match(/^([^:]*)(:443)?$/); + if (!m) { + throw new Error(`statusbot: Matrix server port not 443 (sorry): <${mserver}>`); + } + const matrix_server = m[1]; + // attempt succeeded! + cb(true, {config, home_server, matrix_server, access_token, req_counter}); + }); + }, + (success, state) => { + if (success) { + console.log("statusbot: Successfully got matrix server info."); + cb(state); + } else throw new Error(`statusbot: Failed getting https://${home_server}/.well-known/matrix/server`); } - const matrix_server = m[1]; - cb({config, home_server, matrix_server, access_token, req_counter}); - }); + ); } -function updatePersist(state) { - persistTokens[`${state.config.user_id}|${state.config.room_id}`] = [state.access_token, state.req_counter]; +function updatePersist() { + persistTokens[`${gstate.config.user_id}|${gstate.config.room_id}`] = [gstate.access_token, gstate.req_counter]; persist.setItemSync("access_tokens", persistTokens); } // Sets access token in state. // cb: (success: bool, body: string) => () -function matrixLogin(state, cb) { +function matrixLogin(cb) { const data = JSON.stringify({ type: "m.login.password", - identifier: {type: "m.id.user", user: state.config.user_id}, - password: state.config.password, + identifier: {type: "m.id.user", user: gstate.config.user_id}, + password: gstate.config.password, }); - fetch("POST", {}, state.matrix_server, "/_matrix/client/v3/login", data, (status, body) => { + fetch("POST", {}, gstate.matrix_server, "/_matrix/client/v3/login", data, (status, body) => { if (status != 200) { cb(false, body); return; } try { const response = JSON.parse(body); - state.access_token = response.access_token; - state.req_counter = 1; - updatePersist(state); + gstate.access_token = response.access_token; + gstate.req_counter = 1; + updatePersist(); cb(true, body); } catch (e) { cb(false, body); @@ -118,20 +220,20 @@ function matrixLogin(state, cb) { // Status 401: access token invalid // Status 200: success // Anything else: ? (see body?) -function matrixSendMsg(state, text, cb) { - if (state.access_token == undefined) { cb(401); return; } +function matrixSendMsg(text, cb) { + if (gstate.access_token == undefined) { cb(401); return; } - const headers = {Authorization: `Bearer ${state.access_token}`}; - const url = `/_matrix/client/v3/rooms/${state.config.room_id}/send/m.room.message/${state.req_counter}`; - state.req_counter++; - updatePersist(state); // req_counter changed + const headers = {Authorization: `Bearer ${gstate.access_token}`}; + const url = `/_matrix/client/v3/rooms/${gstate.config.room_id}/send/m.room.message/${gstate.req_counter}`; + gstate.req_counter++; + updatePersist(); // req_counter changed const data = JSON.stringify({ msgtype: "m.text", body: text, }); - fetch("PUT", headers, state.matrix_server, url, data, (status, body) => { + fetch("PUT", headers, gstate.matrix_server, url, data, (status, body) => { cb(status, body); }); } @@ -146,51 +248,72 @@ function logFailure(message, cb) { // Tries to send a message, trying login if the access token is invalid. // cb: (success: bool) => () -function matrixSendMsgLogin(state, text, cb) { - matrixSendMsg(state, text, (status, body) => { +function matrixSendMsgLogin(text, cb) { + matrixSendMsg(text, (status, body) => { switch (status) { case 200: cb(true); break; case 401: - matrixLogin(state, (success, body) => { + matrixLogin((success, body) => { if (!success) { - logFailure(`Failed to log in: ${body}`, () => cb(false)); + logFailure(`Failed to log in, error: ${body}`, () => cb(false)); return; } - matrixSendMsg(state, text, (status, body) => { + matrixSendMsg(text, (status, body) => { switch (status) { case 200: cb(true); return; - case 401: logFailure(`401 even after login: ${body}`, () => cb(false)); break; - default: logFailure(`Failed to send message: ${body}`, () => cb(false)); break; + case 401: logFailure(`401 even after login, error: ${body}`, () => cb(false)); break; + default: logFailure(`Failed to send message, error: ${body}`, () => cb(false)); break; } }); }); break; default: - logFailure(`Failed to send message: ${body}`, () => cb(false)); + logFailure(`Failed to send message, error: ${body}`, () => cb(false)); break; } - }) + }); } module.exports = function(app, io, _moddir) { moddir = _moddir; - const config = require("./matrix.json"); - const accounts = require("./accounts.json"); + let config, accounts; + try { + config = require("./matrix.json"); + accounts = require("./accounts.json"); + } catch (e) { + console.error(e); + return false; + } + + const ratelimit = new PRatelimit(1000); augmentConfig(config, state => { + gstate = state; + app.post("/statusbot", bodyParser.json(), cmn.authgen(accounts), (req, res) => { if (typeof req.body.sender != "string" || typeof req.body.text != "string") { return res.sendStatus(400); } - matrixSendMsgLogin(state, `[${req.body.sender}] ${req.body.text}`, success => { - if (success) res.sendStatus(200); - else res.sendStatus(503); // service unavailable + ratelimit.submit(rlcb => { + ptimeout(5000, + cb => matrixSendMsgLogin(`[${req.body.sender}] ${req.body.text}`, cb), + (finished, success) => { + if (!finished) { + res.sendStatus(504); // gateway timeout + logFailure(`Timed out on message: [${req.body.sender}] ${req.body.text}`, () => {}); + } else if (!success) { + res.sendStatus(503); // service unavailable + logFailure(`Unsuccessful for message: [${req.body.sender}] ${req.body.text}`, () => {}); + } else res.sendStatus(200); + rlcb(); + } + ); }); }); }); diff --git a/modules/timetrack/timetrack.js b/modules/timetrack/timetrack.js index 886ea1e..b654ef1 100644 --- a/modules/timetrack/timetrack.js +++ b/modules/timetrack/timetrack.js @@ -159,7 +159,7 @@ module.exports=function(app,io,_moddir){ }); }); - app.all(["/timetrack","/timetrack/*"],authMiddleware); //for all the other endpoints + app.all(["/timetrack","/timetrack/*rest"],authMiddleware); //for all the other endpoints app.get("/timetrack",function(req,res){ res.sendFile(moddir+"/timetrack.html"); diff --git a/modules/timetrack2/timetrack2.js b/modules/timetrack2/timetrack2.js index cb22132..f3d35ae 100644 --- a/modules/timetrack2/timetrack2.js +++ b/modules/timetrack2/timetrack2.js @@ -163,7 +163,7 @@ module.exports = function(app, io, _moddir){ }); }); - app.all([ROOT_ENDPOINT, ROOT_ENDPOINT+"/*"], authMiddleware); //for all the other endpoints + app.all([ROOT_ENDPOINT, ROOT_ENDPOINT+"/*rest"], authMiddleware); //for all the other endpoints app.get(ROOT_ENDPOINT, function(req, res){ res.sendFile(moddir + "/timetrack.html"); diff --git a/modules/timetrack3/timetrack3.js b/modules/timetrack3/timetrack3.js index dbb0e5b..ba1d9c0 100644 --- a/modules/timetrack3/timetrack3.js +++ b/modules/timetrack3/timetrack3.js @@ -202,7 +202,7 @@ module.exports = function(app, io, _moddir){ }); // for all the other endpoints, authorisation is needed - app.all([ROOT_ENDPOINT, ROOT_ENDPOINT + "/*"], authMiddleware); + app.all([ROOT_ENDPOINT, ROOT_ENDPOINT + "/*rest"], authMiddleware); // - -> html app.get(ROOT_ENDPOINT, (req, res) => { diff --git a/modules/todo/todo.js b/modules/todo/todo.js index b5d3417..239dc19 100644 --- a/modules/todo/todo.js +++ b/modules/todo/todo.js @@ -170,7 +170,7 @@ module.exports=function(app,io,_moddir){ }); }); - app.all(["/todo","/todo/*"],authMiddleware); //for all the other endpoints + app.all(["/todo","/todo/*rest"],authMiddleware); //for all the other endpoints app.get("/todo",function(req,res){ var contents=fs.readFileSync(moddir+"/todo.html","utf8"); diff --git a/modules/unicode/aliases.txt b/modules/unicode/aliases.txt index ea23cab..bb1fba6 100644 --- a/modules/unicode/aliases.txt +++ b/modules/unicode/aliases.txt @@ -1,3 +1,5 @@ 0x039B lambda 0x03BB lambda 0x2218 composition +0x2248 approximately equal to +0x00AC logical not diff --git a/modules/unicode/index.html b/modules/unicode/index.html index 407d6b5..25eba88 100644 --- a/modules/unicode/index.html +++ b/modules/unicode/index.html @@ -24,9 +24,13 @@ body { .table > div > span.space-spacer { width: 1em; } -.table { - max-height: 300px; - overflow-y: scroll; +h3.table { + display: inline-block; + margin: 10px 0 10px 0; + user-select: none; +} +details { + margin-top: 15px; } .invisible { display: none; @@ -206,10 +210,9 @@ window.addEventListener("load", function() { </div> </div> - <h3>Codepoints <span id="codepoints_num"></span></h3> - <div id="codepoints" class="table"></div> + <details open><summary><h3 class="table">Codepoints <span id="codepoints_num"></span></h3></summary><div id="codepoints" class="table"></div></details> - <h3>Found in descriptions <span id="search_num"></span></h3> - <div id="search" class="table"></div> + <details open><summary><h3 class="table">Found in descriptions <span id="search_num"></span></h3></summary><div id="search" class="table"></div></details> </body> </html> +<!-- vim: set sw=4 ts=4 noet: --> diff --git a/modules/up_log/.gitignore b/modules/up_log/.gitignore new file mode 100644 index 0000000..955d2cc --- /dev/null +++ b/modules/up_log/.gitignore @@ -0,0 +1,2 @@ +accounts.json +log.txt diff --git a/modules/up_log/up_log.js b/modules/up_log/up_log.js new file mode 100644 index 0000000..609d705 --- /dev/null +++ b/modules/up_log/up_log.js @@ -0,0 +1,29 @@ +const cmn = require("../$common.js"); +const fs = require("fs"); +const bodyParser = require("body-parser"); + +let moddir = null; + +module.exports=function(app,io,_moddir){ + moddir = _moddir; + + let config, accounts; + try { + accounts = require("./accounts.json"); + } catch (e) { + console.error(e); + return false; + } + + app.post("/up_log/log", bodyParser.json(), cmn.authgen(accounts), (req,res) => { + if (typeof req.body.machine != "string") { + return res.sendStatus(400); + } + + const machine = req.body.machine.replace(/[^a-zA-Z0-9._-]/g, ""); + + fs.appendFileSync(moddir + "/log.txt", new Date().toISOString() + " machine=" + machine + "\n"); + + res.status(200).end(); + }); +}; diff --git a/modules/zelfoverhoor/zelfoverhoor.js b/modules/zelfoverhoor/zelfoverhoor.js index 03de63c..9474378 100644 --- a/modules/zelfoverhoor/zelfoverhoor.js +++ b/modules/zelfoverhoor/zelfoverhoor.js @@ -6,7 +6,12 @@ var moddir; var dbdir=cmn.persistdir+"/zelfoverhoor"; mkdirp.sync(dbdir); -var accounts=require("./accounts.json"); +var accounts=null; +try { + accounts=require("./accounts.json"); +} catch (e) { + console.error(e); +} var SHUFFLE_QUESTIONS=false; @@ -97,6 +102,9 @@ function canAccessFile(fname){ module.exports=function(app,io,_moddir){ moddir=_moddir; + // Failed to load accounts.json + if(accounts==null)return false; + if(canAccessFile(dbdir+"/questiondb.json")&&canAccessFile(dbdir+"/questionsets.json")&&canAccessFile(dbdir+"/userlists.json")){ questiondb=JSON.parse(fs.readFileSync(dbdir+"/questiondb.json")); questionsets=JSON.parse(fs.readFileSync(dbdir+"/questionsets.json")); @@ -109,7 +117,7 @@ module.exports=function(app,io,_moddir){ persistDB(); } - app.all("/zelfoverhoor*",function(req,res,next){ + app.all("/zelfoverhoor*rest",function(req,res,next){ res.header("Cache-Control","private, no-cache, no-store, must-revalidate"); res.header("Expires","-1"); res.header("Pragma","no-cache"); @@ -153,7 +161,7 @@ module.exports=function(app,io,_moddir){ res.send(JSON.stringify(resset)); }); - app.use(["/zelfoverhoor/docent","/zelfoverhoor/docent/*"],cmn.authgen(accounts)); + app.use(["/zelfoverhoor/docent","/zelfoverhoor/docent/*rest"],cmn.authgen(accounts)); app.get("/zelfoverhoor/docent",function(req,res){ fs.readFile(moddir+"/docent.html",function(err,data){ if(err)throw err; |