mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8124abab34 | ||
|
|
4e39975239 | ||
|
|
e506784741 | ||
|
|
5d19c4ab45 | ||
|
|
cc6c3f46fe | ||
|
|
ffbca83aa3 | ||
|
|
4bd32c8a4b | ||
|
|
260cc21b67 | ||
|
|
fb57d91b83 | ||
|
|
414af99644 | ||
|
|
134b6de2b1 | ||
|
|
a31baa4ebd | ||
|
|
3603b82eac | ||
|
|
937dde592f | ||
|
|
24f0fffb41 | ||
|
|
e4e7e42c65 | ||
|
|
44add9d6ac | ||
|
|
c39ba3bdc7 | ||
|
|
13a33a9497 | ||
|
|
46c66667c1 |
@@ -43,3 +43,6 @@ lib/
|
||||
|
||||
# Mac DS_Store files
|
||||
.DS_Store
|
||||
|
||||
# Folder created by FileSystemAdapter
|
||||
/files
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### 2.2.1 (3/22/2016)
|
||||
|
||||
* New: Add FileSystemAdapter file adapter [\#1098](https://github.com/ParsePlatform/parse-server/pull/1098) (dtsolis)
|
||||
* New: Enabled CLP editing [\#1128](https://github.com/ParsePlatform/parse-server/pull/1128) (drew-gross)
|
||||
* Improvement: Reduces the number of connections to mongo created [\#1111](https://github.com/ParsePlatform/parse-server/pull/1111) (flovilmart)
|
||||
* Improvement: Make ParseServer a class [\#980](https://github.com/ParsePlatform/parse-server/pull/980) (flovilmart)
|
||||
* Fix: Adds support for plain object in $add, $addUnique, $remove [\#1114](https://github.com/ParsePlatform/parse-server/pull/1114) (flovilmart)
|
||||
* Fix: Generates default CLP, freezes objects [\#1132](https://github.com/ParsePlatform/parse-server/pull/1132) (flovilmart)
|
||||
* Fix: Properly sets installationId on creating session with 3rd party auth [\#1110](https://github.com/ParsePlatform/parse-server/pull/1110) (flovilmart)
|
||||
|
||||
### 2.2.0 (3/18/2016)
|
||||
|
||||
* New Feature: Real-time functionality with Live Queries! [\#1092](https://github.com/ParsePlatform/parse-server/pull/1092) (wangmengyan95)
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.1",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -35,7 +35,7 @@
|
||||
"mongodb": "~2.1.0",
|
||||
"multer": "^1.1.0",
|
||||
"node-gcm": "^0.14.0",
|
||||
"parse": "^1.7.0",
|
||||
"parse": "^1.8.0",
|
||||
"redis": "^2.5.0-1",
|
||||
"request": "^2.65.0",
|
||||
"tv4": "^1.2.7",
|
||||
@@ -57,7 +57,7 @@
|
||||
"flow-bin": "^0.22.0",
|
||||
"gaze": "^0.5.2",
|
||||
"jasmine": "^2.3.2",
|
||||
"mongodb-runner": "^3.1.15",
|
||||
"mongodb-runner": "3.1.15",
|
||||
"nodemon": "^1.8.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -2,6 +2,7 @@ var FilesController = require('../src/Controllers/FilesController').FilesControl
|
||||
var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter;
|
||||
var S3Adapter = require("../src/Adapters/Files/S3Adapter").S3Adapter;
|
||||
var GCSAdapter = require("../src/Adapters/Files/GCSAdapter").GCSAdapter;
|
||||
var FileSystemAdapter = require("../src/Adapters/Files/FileSystemAdapter").FileSystemAdapter;
|
||||
var Config = require("../src/Config");
|
||||
|
||||
var FCTestFactory = require("./FilesControllerTestFactory");
|
||||
@@ -49,4 +50,15 @@ describe("FilesController",()=>{
|
||||
} else if (!process.env.TRAVIS) {
|
||||
console.log("set GCP_PROJECT_ID, GCP_KEYFILE_PATH, and GCS_BUCKET to test GCSAdapter")
|
||||
}
|
||||
|
||||
try {
|
||||
// Test the file system adapter
|
||||
var fsAdapter = new FileSystemAdapter({
|
||||
filesSubDirectory: 'sub1/sub2'
|
||||
});
|
||||
|
||||
FCTestFactory.testAdapter("FileSystemAdapter", fsAdapter);
|
||||
} catch (e) {
|
||||
console.log("Give write access to the file system to test the FileSystemAdapter. Error: " + e);
|
||||
}
|
||||
});
|
||||
|
||||
+12
-4
@@ -204,13 +204,11 @@ describe('OAuth', function() {
|
||||
myoauth: getMockMyOauthProvider().authData
|
||||
}
|
||||
};
|
||||
var headers = {'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json' }
|
||||
|
||||
var options = {
|
||||
headers: {'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'X-Parse-Installation-Id': 'yolo',
|
||||
'Content-Type': 'application/json' },
|
||||
url: 'http://localhost:8378/1/users',
|
||||
body: JSON.stringify(jsonBody)
|
||||
@@ -224,9 +222,19 @@ describe('OAuth', function() {
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
ok(b.sessionToken);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
done();
|
||||
var sessionToken = b.sessionToken;
|
||||
var q = new Parse.Query("_Session");
|
||||
q.equalTo('sessionToken', sessionToken);
|
||||
q.first({useMasterKey: true}).then((res) => {
|
||||
expect(res.get("installationId")).toEqual('yolo');
|
||||
done();
|
||||
}).fail((err) => {
|
||||
fail('should not fail fetching the session');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -600,6 +600,49 @@ describe('Parse.Object testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("addUnique with object", function(done) {
|
||||
var x1 = new Parse.Object('X');
|
||||
x1.set('stuff', [ 1, {'hello': 'world'}, {'foo': 'bar'}]);
|
||||
x1.save().then(() => {
|
||||
var objectId = x1.id;
|
||||
var x2 = new Parse.Object('X', {objectId: objectId});
|
||||
x2.addUnique('stuff', {'hello': 'world'});
|
||||
x2.addUnique('stuff', {'bar': 'baz'});
|
||||
expect(x2.get('stuff')).toEqual([{'hello': 'world'}, {'bar': 'baz'}]);
|
||||
return x2.save();
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('X');
|
||||
return query.get(x1.id);
|
||||
}).then((x3) => {
|
||||
expect(x3.get('stuff')).toEqual([1, {'hello': 'world'}, {'foo': 'bar'}, {'bar': 'baz'}]);
|
||||
done();
|
||||
}, (error) => {
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("removes with object", function(done) {
|
||||
var x1 = new Parse.Object('X');
|
||||
x1.set('stuff', [ 1, {'hello': 'world'}, {'foo': 'bar'}]);
|
||||
x1.save().then(() => {
|
||||
var objectId = x1.id;
|
||||
var x2 = new Parse.Object('X', {objectId: objectId});
|
||||
x2.remove('stuff', {'hello': 'world'});
|
||||
expect(x2.get('stuff')).toEqual([]);
|
||||
return x2.save();
|
||||
}).then(() => {
|
||||
var query = new Parse.Query('X');
|
||||
return query.get(x1.id);
|
||||
}).then((x3) => {
|
||||
expect(x3.get('stuff')).toEqual([1, {'foo': 'bar'}]);
|
||||
done();
|
||||
}, (error) => {
|
||||
fail(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("dirty attributes", function(done) {
|
||||
var object = new Parse.Object("TestObject");
|
||||
object.set("cat", "good");
|
||||
@@ -1794,14 +1837,14 @@ describe('Parse.Object testing', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should have undefined includes when object is missing', (done) => {
|
||||
let obj1 = new Parse.Object("AnObject");
|
||||
let obj2 = new Parse.Object("AnObject");
|
||||
|
||||
|
||||
Parse.Object.saveAll([obj1, obj2]).then(() => {
|
||||
obj1.set("obj", obj2);
|
||||
// Save the pointer, delete the pointee
|
||||
// Save the pointer, delete the pointee
|
||||
return obj1.save().then(() => { return obj2.destroy() });
|
||||
}).then(() => {
|
||||
let query = new Parse.Query("AnObject");
|
||||
@@ -1823,7 +1866,7 @@ describe('Parse.Object testing', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should have undefined includes when object is missing on deeper path', (done) => {
|
||||
let obj1 = new Parse.Object("AnObject");
|
||||
let obj2 = new Parse.Object("AnObject");
|
||||
@@ -1831,7 +1874,7 @@ describe('Parse.Object testing', () => {
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
obj1.set("obj", obj2);
|
||||
obj2.set("obj", obj3);
|
||||
// Save the pointer, delete the pointee
|
||||
// Save the pointer, delete the pointee
|
||||
return Parse.Object.saveAll([obj1, obj2]).then(() => { return obj3.destroy() });
|
||||
}).then(() => {
|
||||
let query = new Parse.Query("AnObject");
|
||||
|
||||
+29
-1
@@ -1768,9 +1768,37 @@ describe('Parse.User testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('user get session from token', (done) => {
|
||||
it('user get session from token on signup', (done) => {
|
||||
Parse.Promise.as().then(() => {
|
||||
return Parse.User.signUp("finn", "human", { foo: "bar" });
|
||||
}).then((user) => {
|
||||
request.get({
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Session-Token': user.getSessionToken(),
|
||||
'X-Parse-REST-API-Key': 'rest'
|
||||
},
|
||||
url: 'http://localhost:8378/1/sessions/me',
|
||||
}, (error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(typeof b.sessionToken).toEqual('string');
|
||||
expect(typeof b.createdWith).toEqual('object');
|
||||
expect(b.createdWith.action).toEqual('signup');
|
||||
expect(typeof b.user).toEqual('object');
|
||||
expect(b.user.objectId).toEqual(user.id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('user get session from token on login', (done) => {
|
||||
Parse.Promise.as().then(() => {
|
||||
return Parse.User.signUp("finn", "human", { foo: "bar" });
|
||||
}).then((user) => {
|
||||
return Parse.User.logOut().then(() => {
|
||||
return Parse.User.logIn("finn", "human");
|
||||
})
|
||||
}).then((user) => {
|
||||
request.get({
|
||||
headers: {
|
||||
|
||||
+9
-2
@@ -51,8 +51,15 @@ var server = app.listen(port);
|
||||
// Prevent reinitializing the server from clobbering Cloud Code
|
||||
delete defaultConfiguration.cloud;
|
||||
|
||||
var currentConfiguration;
|
||||
// Allows testing specific configurations of Parse Server
|
||||
var setServerConfiguration = configuration => {
|
||||
// the configuration hasn't changed
|
||||
if (configuration === currentConfiguration) {
|
||||
return;
|
||||
}
|
||||
DatabaseAdapter.clearDatabaseSettings();
|
||||
currentConfiguration = configuration;
|
||||
server.close();
|
||||
cache.clearCache();
|
||||
app = express();
|
||||
@@ -72,17 +79,17 @@ Parse.serverURL = 'http://localhost:' + port + '/1';
|
||||
Parse.Promise.disableAPlusCompliant();
|
||||
|
||||
beforeEach(function(done) {
|
||||
restoreServerConfiguration();
|
||||
Parse.initialize('test', 'test', 'test');
|
||||
Parse.serverURL = 'http://localhost:' + port + '/1';
|
||||
Parse.User.enableUnsafeCurrentUser();
|
||||
done();
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
restoreServerConfiguration();
|
||||
Parse.User.logOut().then(() => {
|
||||
return clearData();
|
||||
}).then(() => {
|
||||
DatabaseAdapter.clearDatabaseSettings();
|
||||
done();
|
||||
}, (error) => {
|
||||
console.log('error in clearData', error);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
var request = require('request');
|
||||
var parseServerPackage = require('../package.json');
|
||||
var MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions');
|
||||
var ParseServer = require("../src/index");
|
||||
var express = require('express');
|
||||
|
||||
describe('server', () => {
|
||||
it('requires a master key and app id', done => {
|
||||
@@ -168,4 +170,62 @@ describe('server', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('can create a parse-server', done => {
|
||||
var parseServer = new ParseServer.default({
|
||||
appId: "aTestApp",
|
||||
masterKey: "aTestMasterKey",
|
||||
serverURL: "http://localhost:12666/parse",
|
||||
databaseURI: 'mongodb://localhost:27017/aTestApp'
|
||||
});
|
||||
|
||||
expect(Parse.applicationId).toEqual("aTestApp");
|
||||
var app = express();
|
||||
app.use('/parse', parseServer.app);
|
||||
|
||||
var server = app.listen(12666);
|
||||
var obj = new Parse.Object("AnObject");
|
||||
var objId;
|
||||
obj.save().then((obj) => {
|
||||
objId = obj.id;
|
||||
var q = new Parse.Query("AnObject");
|
||||
return q.first();
|
||||
}).then((obj) => {
|
||||
expect(obj.id).toEqual(objId);
|
||||
server.close();
|
||||
done();
|
||||
}).fail((err) => {
|
||||
server.close();
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('can create a parse-server', done => {
|
||||
var parseServer = ParseServer.ParseServer({
|
||||
appId: "anOtherTestApp",
|
||||
masterKey: "anOtherTestMasterKey",
|
||||
serverURL: "http://localhost:12667/parse",
|
||||
databaseURI: 'mongodb://localhost:27017/anotherTstApp'
|
||||
});
|
||||
|
||||
expect(Parse.applicationId).toEqual("anOtherTestApp");
|
||||
var app = express();
|
||||
app.use('/parse', parseServer);
|
||||
|
||||
var server = app.listen(12667);
|
||||
var obj = new Parse.Object("AnObject");
|
||||
var objId;
|
||||
obj.save().then((obj) => {
|
||||
objId = obj.id;
|
||||
var q = new Parse.Query("AnObject");
|
||||
return q.first();
|
||||
}).then((obj) => {
|
||||
expect(obj.id).toEqual(objId);
|
||||
server.close();
|
||||
done();
|
||||
}).fail((err) => {
|
||||
server.close();
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// FileSystemAdapter
|
||||
//
|
||||
// Stores files in local file system
|
||||
// Requires write access to the server's file system.
|
||||
|
||||
import { FilesAdapter } from './FilesAdapter';
|
||||
import colors from 'colors';
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var pathSep = require('path').sep;
|
||||
|
||||
export class FileSystemAdapter extends FilesAdapter {
|
||||
|
||||
constructor({filesSubDirectory = ''} = {}) {
|
||||
super();
|
||||
|
||||
this._filesDir = filesSubDirectory;
|
||||
this._mkdir(this._getApplicationDir());
|
||||
if (!this._applicationDirExist()) {
|
||||
throw "Files directory doesn't exist.";
|
||||
}
|
||||
}
|
||||
|
||||
// For a given config object, filename, and data, store a file
|
||||
// Returns a promise
|
||||
createFile(config, filename, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let filepath = this._getLocalFilePath(filename);
|
||||
fs.writeFile(filepath, data, (err) => {
|
||||
if(err !== null) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deleteFile(config, filename) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let filepath = this._getLocalFilePath(filename);
|
||||
fs.readFile( filepath , function (err, data) {
|
||||
if(err !== null) {
|
||||
return reject(err);
|
||||
}
|
||||
fs.unlink(filepath, (unlinkErr) => {
|
||||
if(err !== null) {
|
||||
return reject(unlinkErr);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
getFileData(config, filename) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let filepath = this._getLocalFilePath(filename);
|
||||
fs.readFile( filepath , function (err, data) {
|
||||
if(err !== null) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getFileLocation(config, filename) {
|
||||
return (config.mount + '/' + this._getLocalFilePath(filename));
|
||||
}
|
||||
|
||||
/*
|
||||
Helpers
|
||||
--------------- */
|
||||
_getApplicationDir() {
|
||||
if (this._filesDir) {
|
||||
return path.join('files', this._filesDir);
|
||||
} else {
|
||||
return 'files';
|
||||
}
|
||||
}
|
||||
|
||||
_applicationDirExist() {
|
||||
return fs.existsSync(this._getApplicationDir());
|
||||
}
|
||||
|
||||
_getLocalFilePath(filename) {
|
||||
let applicationDir = this._getApplicationDir();
|
||||
if (!fs.existsSync(applicationDir)) {
|
||||
this._mkdir(applicationDir);
|
||||
}
|
||||
return path.join(applicationDir, encodeURIComponent(filename));
|
||||
}
|
||||
|
||||
_mkdir(dirPath) {
|
||||
// snippet found on -> https://gist.github.com/danherbert-epam/3960169
|
||||
let dirs = dirPath.split(pathSep);
|
||||
var root = "";
|
||||
|
||||
while (dirs.length > 0) {
|
||||
var dir = dirs.shift();
|
||||
if (dir === "") { // If directory starts with a /, the first path will be an empty string.
|
||||
root = pathSep;
|
||||
}
|
||||
if (!fs.existsSync(path.join(root, dir))) {
|
||||
try {
|
||||
fs.mkdirSync(path.join(root, dir));
|
||||
}
|
||||
catch (e) {
|
||||
if ( e.code == 'EACCES' ) {
|
||||
throw new Error("PERMISSION ERROR: In order to use the FileSystemAdapter, write access to the server's file system is required.");
|
||||
}
|
||||
}
|
||||
}
|
||||
root = path.join(root, dir, pathSep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FileSystemAdapter;
|
||||
+2
-2
@@ -327,11 +327,11 @@ RestWrite.prototype.transformUser = function() {
|
||||
objectId: this.objectId()
|
||||
},
|
||||
createdWith: {
|
||||
'action': 'login',
|
||||
'action': 'signup',
|
||||
'authProvider': this.storage['authProvider'] || 'password'
|
||||
},
|
||||
restricted: false,
|
||||
installationId: this.data.installationId,
|
||||
installationId: this.auth.installationId,
|
||||
expiresAt: Parse._encode(expiresAt)
|
||||
};
|
||||
if (this.response && this.response.response) {
|
||||
|
||||
@@ -27,17 +27,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
req.body = data;
|
||||
req.params.className = '_User';
|
||||
|
||||
//req.config.userController.setEmailVerifyToken(req.body);
|
||||
|
||||
return super.handleCreate(req);
|
||||
|
||||
// if (req.config.verifyUserEmails) {
|
||||
// // Send email as fire-and-forget once the user makes it into the DB.
|
||||
// p.then(() => {
|
||||
// req.config.userController.sendVerificationEmail(req.body);
|
||||
// });
|
||||
// }
|
||||
// return p;
|
||||
}
|
||||
|
||||
handleUpdate(req) {
|
||||
@@ -102,9 +92,9 @@ export class UsersRouter extends ClassesRouter {
|
||||
let token = 'r:' + cryptoUtils.newToken();
|
||||
user.sessionToken = token;
|
||||
delete user.password;
|
||||
|
||||
|
||||
// Sometimes the authData still has null on that keys
|
||||
// https://github.com/ParsePlatform/parse-server/issues/935
|
||||
// https://github.com/ParsePlatform/parse-server/issues/935
|
||||
if (user.authData) {
|
||||
Object.keys(user.authData).forEach((provider) => {
|
||||
if (user.authData[provider] === null) {
|
||||
@@ -115,7 +105,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
delete user.authData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
req.config.filesController.expandFilesInObject(req.config, user);
|
||||
|
||||
let expiresAt = new Date();
|
||||
@@ -165,14 +155,14 @@ export class UsersRouter extends ClassesRouter {
|
||||
}
|
||||
return Promise.resolve(success);
|
||||
}
|
||||
|
||||
|
||||
handleResetRequest(req) {
|
||||
let { email } = req.body;
|
||||
if (!email) {
|
||||
throw new Parse.Error(Parse.Error.EMAIL_MISSING, "you must provide an email");
|
||||
}
|
||||
let userController = req.config.userController;
|
||||
|
||||
|
||||
return userController.sendPasswordResetEmail(email).then((token) => {
|
||||
return Promise.resolve({
|
||||
response: {}
|
||||
@@ -181,7 +171,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `no user found with email ${email}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
mountRoutes() {
|
||||
this.route('GET', '/users', req => { return this.handleFind(req); });
|
||||
|
||||
+19
-17
@@ -17,7 +17,7 @@
|
||||
var Parse = require('parse/node').Parse;
|
||||
var transform = require('./transform');
|
||||
|
||||
var defaultColumns = {
|
||||
const defaultColumns = Object.freeze({
|
||||
// Contain the default columns for every parse object type (except _Join collection)
|
||||
_Default: {
|
||||
"objectId": {type:'String'},
|
||||
@@ -84,14 +84,14 @@ var defaultColumns = {
|
||||
"sentPerType": {type:'Object'},
|
||||
"failedPerType":{type:'Object'},
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var requiredColumns = {
|
||||
const requiredColumns = Object.freeze({
|
||||
_Product: ["productIdentifier", "icon", "order", "title", "subtitle"],
|
||||
_Role: ["name", "ACL"]
|
||||
}
|
||||
});
|
||||
|
||||
const systemClasses = ['_User', '_Installation', '_Role', '_Session', '_Product'];
|
||||
const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product']);
|
||||
|
||||
// 10 alpha numberic chars + uppercase
|
||||
const userIdRegex = /^[a-zA-Z0-9]{10}$/;
|
||||
@@ -100,7 +100,7 @@ const roleRegex = /^role:.*/;
|
||||
// * permission
|
||||
const publicRegex = /^\*$/
|
||||
|
||||
const permissionKeyRegex = [userIdRegex, roleRegex, publicRegex];
|
||||
const permissionKeyRegex = Object.freeze([userIdRegex, roleRegex, publicRegex]);
|
||||
|
||||
function verifyPermissionKey(key) {
|
||||
let result = permissionKeyRegex.reduce((isGood, regEx) => {
|
||||
@@ -112,13 +112,15 @@ function verifyPermissionKey(key) {
|
||||
}
|
||||
}
|
||||
|
||||
let CLPValidKeys = ['find', 'get', 'create', 'update', 'delete', 'addField'];
|
||||
let DefaultClassLevelPermissions = CLPValidKeys.reduce((perms, key) => {
|
||||
const CLPValidKeys = Object.freeze(['find', 'get', 'create', 'update', 'delete', 'addField']);
|
||||
let DefaultClassLevelPermissions = () => {
|
||||
return CLPValidKeys.reduce((perms, key) => {
|
||||
perms[key] = {
|
||||
'*': true
|
||||
};
|
||||
return perms;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function validateCLP(perms) {
|
||||
if (!perms) {
|
||||
@@ -899,21 +901,21 @@ function mongoSchemaToSchemaAPIResponse(schema) {
|
||||
fields: mongoSchemaAPIResponseFields(schema),
|
||||
};
|
||||
|
||||
let classLevelPermissions = DefaultClassLevelPermissions;
|
||||
let classLevelPermissions = DefaultClassLevelPermissions();
|
||||
if (schema._metadata && schema._metadata.class_permissions) {
|
||||
classLevelPermissions = Object.assign(classLevelPermissions, schema._metadata.class_permissions);
|
||||
classLevelPermissions = Object.assign({}, classLevelPermissions, schema._metadata.class_permissions);
|
||||
}
|
||||
result.classLevelPermissions = classLevelPermissions;
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
load: load,
|
||||
classNameIsValid: classNameIsValid,
|
||||
invalidClassNameMessage: invalidClassNameMessage,
|
||||
schemaAPITypeToMongoFieldType: schemaAPITypeToMongoFieldType,
|
||||
buildMergedSchemaObject: buildMergedSchemaObject,
|
||||
mongoFieldTypeToSchemaAPIType: mongoFieldTypeToSchemaAPIType,
|
||||
export {
|
||||
load,
|
||||
classNameIsValid,
|
||||
invalidClassNameMessage,
|
||||
schemaAPITypeToMongoFieldType,
|
||||
buildMergedSchemaObject,
|
||||
mongoFieldTypeToSchemaAPIType,
|
||||
mongoSchemaToSchemaAPIResponse,
|
||||
systemClasses,
|
||||
};
|
||||
|
||||
@@ -53,6 +53,7 @@ let features = {
|
||||
removeClass: true,
|
||||
clearAllDataFromClass: false,
|
||||
exportClass: false,
|
||||
editClassLevelPermissions: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+195
-179
@@ -48,6 +48,7 @@ import { SessionsRouter } from './Routers/SessionsRouter';
|
||||
import { setFeature } from './features';
|
||||
import { UserController } from './Controllers/UserController';
|
||||
import { UsersRouter } from './Routers/UsersRouter';
|
||||
import { FileSystemAdapter } from './Adapters/Files/FileSystemAdapter';
|
||||
|
||||
// Mutate the Parse object to add the Cloud Code handlers
|
||||
addParseCloud();
|
||||
@@ -78,187 +79,205 @@ addParseCloud();
|
||||
// "javascriptKey": optional key from Parse dashboard
|
||||
// "push": optional key from configure push
|
||||
|
||||
function ParseServer({
|
||||
appId = requiredParameter('You must provide an appId!'),
|
||||
masterKey = requiredParameter('You must provide a masterKey!'),
|
||||
appName,
|
||||
databaseAdapter,
|
||||
filesAdapter,
|
||||
push,
|
||||
loggerAdapter,
|
||||
databaseURI = DatabaseAdapter.defaultDatabaseURI,
|
||||
databaseOptions,
|
||||
cloud,
|
||||
collectionPrefix = '',
|
||||
clientKey,
|
||||
javascriptKey,
|
||||
dotNetKey,
|
||||
restAPIKey,
|
||||
fileKey = 'invalid-file-key',
|
||||
facebookAppIds = [],
|
||||
enableAnonymousUsers = true,
|
||||
allowClientClassCreation = true,
|
||||
oauth = {},
|
||||
serverURL = requiredParameter('You must provide a serverURL!'),
|
||||
maxUploadSize = '20mb',
|
||||
verifyUserEmails = false,
|
||||
emailAdapter,
|
||||
publicServerURL,
|
||||
customPages = {
|
||||
invalidLink: undefined,
|
||||
verifyEmailSuccess: undefined,
|
||||
choosePassword: undefined,
|
||||
passwordResetSuccess: undefined
|
||||
},
|
||||
liveQuery = {}
|
||||
}) {
|
||||
setFeature('serverVersion', parseServerPackage.version);
|
||||
// Initialize the node client SDK automatically
|
||||
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
|
||||
Parse.serverURL = serverURL;
|
||||
export default class ParseServer {
|
||||
|
||||
if (databaseAdapter) {
|
||||
DatabaseAdapter.setAdapter(databaseAdapter);
|
||||
}
|
||||
constructor({
|
||||
appId = requiredParameter('You must provide an appId!'),
|
||||
masterKey = requiredParameter('You must provide a masterKey!'),
|
||||
appName,
|
||||
databaseAdapter,
|
||||
filesAdapter,
|
||||
push,
|
||||
loggerAdapter,
|
||||
databaseURI = DatabaseAdapter.defaultDatabaseURI,
|
||||
databaseOptions,
|
||||
cloud,
|
||||
collectionPrefix = '',
|
||||
clientKey,
|
||||
javascriptKey,
|
||||
dotNetKey,
|
||||
restAPIKey,
|
||||
fileKey = 'invalid-file-key',
|
||||
facebookAppIds = [],
|
||||
enableAnonymousUsers = true,
|
||||
allowClientClassCreation = true,
|
||||
oauth = {},
|
||||
serverURL = requiredParameter('You must provide a serverURL!'),
|
||||
maxUploadSize = '20mb',
|
||||
verifyUserEmails = false,
|
||||
emailAdapter,
|
||||
publicServerURL,
|
||||
customPages = {
|
||||
invalidLink: undefined,
|
||||
verifyEmailSuccess: undefined,
|
||||
choosePassword: undefined,
|
||||
passwordResetSuccess: undefined
|
||||
},
|
||||
liveQuery = {}
|
||||
}) {
|
||||
setFeature('serverVersion', parseServerPackage.version);
|
||||
// Initialize the node client SDK automatically
|
||||
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
|
||||
Parse.serverURL = serverURL;
|
||||
|
||||
if (databaseURI) {
|
||||
DatabaseAdapter.setAppDatabaseURI(appId, databaseURI);
|
||||
}
|
||||
|
||||
if (databaseOptions) {
|
||||
DatabaseAdapter.setAppDatabaseOptions(appId, databaseOptions);
|
||||
}
|
||||
|
||||
if (cloud) {
|
||||
addParseCloud();
|
||||
if (typeof cloud === 'function') {
|
||||
cloud(Parse)
|
||||
} else if (typeof cloud === 'string') {
|
||||
require(cloud);
|
||||
} else {
|
||||
throw "argument 'cloud' must either be a string or a function";
|
||||
if (databaseAdapter) {
|
||||
DatabaseAdapter.setAdapter(databaseAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
const filesControllerAdapter = loadAdapter(filesAdapter, () => {
|
||||
return new GridStoreAdapter(databaseURI);
|
||||
});
|
||||
// Pass the push options too as it works with the default
|
||||
const pushControllerAdapter = loadAdapter(push && push.adapter, ParsePushAdapter, push);
|
||||
const loggerControllerAdapter = loadAdapter(loggerAdapter, FileLoggerAdapter);
|
||||
const emailControllerAdapter = loadAdapter(emailAdapter);
|
||||
// We pass the options and the base class for the adatper,
|
||||
// Note that passing an instance would work too
|
||||
const filesController = new FilesController(filesControllerAdapter, appId);
|
||||
const pushController = new PushController(pushControllerAdapter, appId);
|
||||
const loggerController = new LoggerController(loggerControllerAdapter, appId);
|
||||
const hooksController = new HooksController(appId, collectionPrefix);
|
||||
const userController = new UserController(emailControllerAdapter, appId, { verifyUserEmails });
|
||||
const liveQueryController = new LiveQueryController(liveQuery);
|
||||
if (databaseOptions) {
|
||||
DatabaseAdapter.setAppDatabaseOptions(appId, databaseOptions);
|
||||
}
|
||||
|
||||
if (databaseURI) {
|
||||
DatabaseAdapter.setAppDatabaseURI(appId, databaseURI);
|
||||
}
|
||||
|
||||
cache.apps.set(appId, {
|
||||
masterKey: masterKey,
|
||||
serverURL: serverURL,
|
||||
collectionPrefix: collectionPrefix,
|
||||
clientKey: clientKey,
|
||||
javascriptKey: javascriptKey,
|
||||
dotNetKey: dotNetKey,
|
||||
restAPIKey: restAPIKey,
|
||||
fileKey: fileKey,
|
||||
facebookAppIds: facebookAppIds,
|
||||
filesController: filesController,
|
||||
pushController: pushController,
|
||||
loggerController: loggerController,
|
||||
hooksController: hooksController,
|
||||
userController: userController,
|
||||
verifyUserEmails: verifyUserEmails,
|
||||
allowClientClassCreation: allowClientClassCreation,
|
||||
authDataManager: authDataManager(oauth, enableAnonymousUsers),
|
||||
appName: appName,
|
||||
publicServerURL: publicServerURL,
|
||||
customPages: customPages,
|
||||
liveQueryController: liveQueryController
|
||||
});
|
||||
|
||||
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
|
||||
if (process.env.FACEBOOK_APP_ID) {
|
||||
cache.apps.get(appId)['facebookAppIds'].push(process.env.FACEBOOK_APP_ID);
|
||||
}
|
||||
|
||||
Config.validate(cache.apps.get(appId));
|
||||
|
||||
// This app serves the Parse API directly.
|
||||
// It's the equivalent of https://api.parse.com/1 in the hosted Parse API.
|
||||
var api = express();
|
||||
//api.use("/apps", express.static(__dirname + "/public"));
|
||||
// File handling needs to be before default middlewares are applied
|
||||
api.use('/', middlewares.allowCrossDomain, new FilesRouter().getExpressRouter({
|
||||
maxUploadSize: maxUploadSize
|
||||
}));
|
||||
|
||||
api.use('/', bodyParser.urlencoded({extended: false}), new PublicAPIRouter().expressApp());
|
||||
|
||||
// TODO: separate this from the regular ParseServer object
|
||||
if (process.env.TESTING == 1) {
|
||||
api.use('/', require('./testing-routes').router);
|
||||
}
|
||||
|
||||
api.use(bodyParser.json({ 'type': '*/*' , limit: maxUploadSize }));
|
||||
api.use(middlewares.allowCrossDomain);
|
||||
api.use(middlewares.allowMethodOverride);
|
||||
api.use(middlewares.handleParseHeaders);
|
||||
|
||||
let routers = [
|
||||
new ClassesRouter(),
|
||||
new UsersRouter(),
|
||||
new SessionsRouter(),
|
||||
new RolesRouter(),
|
||||
new AnalyticsRouter(),
|
||||
new InstallationsRouter(),
|
||||
new FunctionsRouter(),
|
||||
new SchemasRouter(),
|
||||
new PushRouter(),
|
||||
new LogsRouter(),
|
||||
new IAPValidationRouter(),
|
||||
new FeaturesRouter(),
|
||||
];
|
||||
|
||||
if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED || process.env.TESTING) {
|
||||
routers.push(new GlobalConfigRouter());
|
||||
}
|
||||
|
||||
if (process.env.PARSE_EXPERIMENTAL_HOOKS_ENABLED || process.env.TESTING) {
|
||||
routers.push(new HooksRouter());
|
||||
}
|
||||
|
||||
let routes = routers.reduce((memo, router) => {
|
||||
return memo.concat(router.routes);
|
||||
}, []);
|
||||
|
||||
let appRouter = new PromiseRouter(routes);
|
||||
|
||||
batch.mountOnto(appRouter);
|
||||
|
||||
api.use(appRouter.expressApp());
|
||||
|
||||
api.use(middlewares.handleParseErrors);
|
||||
|
||||
//This causes tests to spew some useless warnings, so disable in test
|
||||
if (!process.env.TESTING) {
|
||||
process.on('uncaughtException', (err) => {
|
||||
if( err.code === "EADDRINUSE" ) { // user-friendly message for this common error
|
||||
console.log(`Unable to listen on port ${err.port}. The port is already in use.`);
|
||||
process.exit(0);
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
if (cloud) {
|
||||
addParseCloud();
|
||||
if (typeof cloud === 'function') {
|
||||
cloud(Parse)
|
||||
} else if (typeof cloud === 'string') {
|
||||
require(cloud);
|
||||
} else {
|
||||
throw "argument 'cloud' must either be a string or a function";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const filesControllerAdapter = loadAdapter(filesAdapter, () => {
|
||||
return new GridStoreAdapter(databaseURI);
|
||||
});
|
||||
}
|
||||
hooksController.load();
|
||||
// Pass the push options too as it works with the default
|
||||
const pushControllerAdapter = loadAdapter(push && push.adapter, ParsePushAdapter, push);
|
||||
const loggerControllerAdapter = loadAdapter(loggerAdapter, FileLoggerAdapter);
|
||||
const emailControllerAdapter = loadAdapter(emailAdapter);
|
||||
// We pass the options and the base class for the adatper,
|
||||
// Note that passing an instance would work too
|
||||
const filesController = new FilesController(filesControllerAdapter, appId);
|
||||
const pushController = new PushController(pushControllerAdapter, appId);
|
||||
const loggerController = new LoggerController(loggerControllerAdapter, appId);
|
||||
const hooksController = new HooksController(appId, collectionPrefix);
|
||||
const userController = new UserController(emailControllerAdapter, appId, { verifyUserEmails });
|
||||
const liveQueryController = new LiveQueryController(liveQuery);
|
||||
|
||||
return api;
|
||||
cache.apps.set(appId, {
|
||||
masterKey: masterKey,
|
||||
serverURL: serverURL,
|
||||
collectionPrefix: collectionPrefix,
|
||||
clientKey: clientKey,
|
||||
javascriptKey: javascriptKey,
|
||||
dotNetKey: dotNetKey,
|
||||
restAPIKey: restAPIKey,
|
||||
fileKey: fileKey,
|
||||
facebookAppIds: facebookAppIds,
|
||||
filesController: filesController,
|
||||
pushController: pushController,
|
||||
loggerController: loggerController,
|
||||
hooksController: hooksController,
|
||||
userController: userController,
|
||||
verifyUserEmails: verifyUserEmails,
|
||||
allowClientClassCreation: allowClientClassCreation,
|
||||
authDataManager: authDataManager(oauth, enableAnonymousUsers),
|
||||
appName: appName,
|
||||
publicServerURL: publicServerURL,
|
||||
customPages: customPages,
|
||||
maxUploadSize: maxUploadSize,
|
||||
liveQueryController: liveQueryController
|
||||
});
|
||||
|
||||
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
|
||||
if (process.env.FACEBOOK_APP_ID) {
|
||||
cache.apps.get(appId)['facebookAppIds'].push(process.env.FACEBOOK_APP_ID);
|
||||
}
|
||||
|
||||
Config.validate(cache.apps.get(appId));
|
||||
this.config = cache.apps.get(appId);
|
||||
hooksController.load();
|
||||
}
|
||||
|
||||
get app() {
|
||||
return ParseServer.app(this.config);
|
||||
}
|
||||
|
||||
static app({maxUploadSize = '20mb'}) {
|
||||
// This app serves the Parse API directly.
|
||||
// It's the equivalent of https://api.parse.com/1 in the hosted Parse API.
|
||||
var api = express();
|
||||
//api.use("/apps", express.static(__dirname + "/public"));
|
||||
// File handling needs to be before default middlewares are applied
|
||||
api.use('/', middlewares.allowCrossDomain, new FilesRouter().getExpressRouter({
|
||||
maxUploadSize: maxUploadSize
|
||||
}));
|
||||
|
||||
api.use('/', bodyParser.urlencoded({extended: false}), new PublicAPIRouter().expressApp());
|
||||
|
||||
// TODO: separate this from the regular ParseServer object
|
||||
if (process.env.TESTING == 1) {
|
||||
api.use('/', require('./testing-routes').router);
|
||||
}
|
||||
|
||||
api.use(bodyParser.json({ 'type': '*/*' , limit: maxUploadSize }));
|
||||
api.use(middlewares.allowCrossDomain);
|
||||
api.use(middlewares.allowMethodOverride);
|
||||
api.use(middlewares.handleParseHeaders);
|
||||
|
||||
let routers = [
|
||||
new ClassesRouter(),
|
||||
new UsersRouter(),
|
||||
new SessionsRouter(),
|
||||
new RolesRouter(),
|
||||
new AnalyticsRouter(),
|
||||
new InstallationsRouter(),
|
||||
new FunctionsRouter(),
|
||||
new SchemasRouter(),
|
||||
new PushRouter(),
|
||||
new LogsRouter(),
|
||||
new IAPValidationRouter(),
|
||||
new FeaturesRouter(),
|
||||
];
|
||||
|
||||
if (process.env.PARSE_EXPERIMENTAL_CONFIG_ENABLED || process.env.TESTING) {
|
||||
routers.push(new GlobalConfigRouter());
|
||||
}
|
||||
|
||||
if (process.env.PARSE_EXPERIMENTAL_HOOKS_ENABLED || process.env.TESTING) {
|
||||
routers.push(new HooksRouter());
|
||||
}
|
||||
|
||||
let routes = routers.reduce((memo, router) => {
|
||||
return memo.concat(router.routes);
|
||||
}, []);
|
||||
|
||||
let appRouter = new PromiseRouter(routes);
|
||||
|
||||
batch.mountOnto(appRouter);
|
||||
|
||||
api.use(appRouter.expressApp());
|
||||
|
||||
api.use(middlewares.handleParseErrors);
|
||||
|
||||
//This causes tests to spew some useless warnings, so disable in test
|
||||
if (!process.env.TESTING) {
|
||||
process.on('uncaughtException', (err) => {
|
||||
if ( err.code === "EADDRINUSE" ) { // user-friendly message for this common error
|
||||
console.log(`Unable to listen on port ${err.port}. The port is already in use.`);
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
return api;
|
||||
}
|
||||
|
||||
static ParseServer(options) {
|
||||
let server = new ParseServer(options);
|
||||
return server.app;
|
||||
}
|
||||
|
||||
static createLiveQueryServer(httpServer, config) {
|
||||
return new ParseLiveQueryServer(httpServer, config);
|
||||
}
|
||||
}
|
||||
|
||||
function addParseCloud() {
|
||||
@@ -267,12 +286,9 @@ function addParseCloud() {
|
||||
global.Parse = Parse;
|
||||
}
|
||||
|
||||
ParseServer.createLiveQueryServer = function(httpServer, config) {
|
||||
return new ParseLiveQueryServer(httpServer, config);
|
||||
let runServer = function(options) {
|
||||
return ParseServer.ParseServer(options);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ParseServer: ParseServer,
|
||||
S3Adapter: S3Adapter,
|
||||
GCSAdapter: GCSAdapter
|
||||
};
|
||||
export { S3Adapter, GCSAdapter, FileSystemAdapter };
|
||||
export { runServer as ParseServer };
|
||||
|
||||
+2
-2
@@ -97,7 +97,7 @@ function handleParseHeaders(req, res, next) {
|
||||
// Client keys are not required in parse-server, but if any have been configured in the server, validate them
|
||||
// to preserve original behavior.
|
||||
let keys = ["clientKey", "javascriptKey", "dotNetKey", "restAPIKey"];
|
||||
|
||||
|
||||
// We do it with mismatching keys to support no-keys config
|
||||
var keyMismatch = keys.reduce(function(mismatch, key){
|
||||
|
||||
@@ -107,7 +107,7 @@ function handleParseHeaders(req, res, next) {
|
||||
}
|
||||
return mismatch;
|
||||
}, 0);
|
||||
|
||||
|
||||
// All keys mismatch
|
||||
if (keyMismatch == keys.length) {
|
||||
return invalidRequest(req, res);
|
||||
|
||||
@@ -398,6 +398,9 @@ function transformAtom(atom, force, options) {
|
||||
if (FileCoder.isValidJSON(atom)) {
|
||||
return (inArray || inObject ? atom : FileCoder.JSONToDatabase(atom));
|
||||
}
|
||||
if (inArray || inObject) {
|
||||
return atom;
|
||||
}
|
||||
|
||||
if (force) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||
|
||||
Vendored
+13
@@ -66,6 +66,7 @@ const slashedProtocol = {
|
||||
};
|
||||
const querystring = require('querystring');
|
||||
|
||||
/* istanbul ignore next: improve coverage */
|
||||
function urlParse(url, parseQueryString, slashesDenoteHost) {
|
||||
if (url instanceof Url) return url;
|
||||
|
||||
@@ -74,6 +75,7 @@ function urlParse(url, parseQueryString, slashesDenoteHost) {
|
||||
return u;
|
||||
}
|
||||
|
||||
/* istanbul ignore next: improve coverage */
|
||||
Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
|
||||
if (typeof url !== 'string') {
|
||||
throw new TypeError('Parameter "url" must be a string, not ' + typeof url);
|
||||
@@ -394,6 +396,7 @@ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/* istanbul ignore next: improve coverage */
|
||||
function validateHostname(self, rest, hostname) {
|
||||
for (var i = 0, lastPos; i <= hostname.length; ++i) {
|
||||
var code;
|
||||
@@ -429,6 +432,7 @@ function validateHostname(self, rest, hostname) {
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next: improve coverage */
|
||||
function autoEscapeStr(rest) {
|
||||
var newRest = '';
|
||||
var lastPos = 0;
|
||||
@@ -531,6 +535,7 @@ function autoEscapeStr(rest) {
|
||||
}
|
||||
|
||||
// format a parsed object into a url string
|
||||
/* istanbul ignore next: improve coverage */
|
||||
function urlFormat(obj) {
|
||||
// ensure it's an object, and not a string url.
|
||||
// If it's an obj, this is a no-op.
|
||||
@@ -547,6 +552,7 @@ function urlFormat(obj) {
|
||||
return obj.format();
|
||||
}
|
||||
|
||||
/* istanbul ignore next: improve coverage */
|
||||
Url.prototype.format = function() {
|
||||
var auth = this.auth || '';
|
||||
if (auth) {
|
||||
@@ -623,19 +629,23 @@ Url.prototype.format = function() {
|
||||
return protocol + host + pathname + search + hash;
|
||||
};
|
||||
|
||||
/* istanbul ignore next: improve coverage */
|
||||
function urlResolve(source, relative) {
|
||||
return urlParse(source, false, true).resolve(relative);
|
||||
}
|
||||
|
||||
/* istanbul ignore next: improve coverage */
|
||||
Url.prototype.resolve = function(relative) {
|
||||
return this.resolveObject(urlParse(relative, false, true)).format();
|
||||
};
|
||||
|
||||
/* istanbul ignore next: improve coverage */
|
||||
function urlResolveObject(source, relative) {
|
||||
if (!source) return relative;
|
||||
return urlParse(source, false, true).resolveObject(relative);
|
||||
}
|
||||
|
||||
/* istanbul ignore next: improve coverage */
|
||||
Url.prototype.resolveObject = function(relative) {
|
||||
if (typeof relative === 'string') {
|
||||
var rel = new Url();
|
||||
@@ -908,6 +918,7 @@ Url.prototype.resolveObject = function(relative) {
|
||||
return result;
|
||||
};
|
||||
|
||||
/* istanbul ignore next: improve coverage */
|
||||
Url.prototype.parseHost = function() {
|
||||
var host = this.host;
|
||||
var port = portPattern.exec(host);
|
||||
@@ -922,6 +933,7 @@ Url.prototype.parseHost = function() {
|
||||
};
|
||||
|
||||
// About 1.5x faster than the two-arg version of Array#splice().
|
||||
/* istanbul ignore next: improve coverage */
|
||||
function spliceOne(list, index) {
|
||||
for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
|
||||
list[i] = list[k];
|
||||
@@ -931,6 +943,7 @@ function spliceOne(list, index) {
|
||||
var hexTable = new Array(256);
|
||||
for (var i = 0; i < 256; ++i)
|
||||
hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
|
||||
/* istanbul ignore next: improve coverage */
|
||||
function encodeAuth(str) {
|
||||
// faster encodeURIComponent alternative for encoding auth uri components
|
||||
var out = '';
|
||||
|
||||
Reference in New Issue
Block a user