Merge branch 'cordova-hcp' into devel

Conflicts:
	packages/deps/package.js
	packages/reload/reload.js
	tools/tests/apps/hot-code-push-test/.meteor/versions
This commit is contained in:
Matthew Arbesfeld
2014-08-29 12:08:34 -07:00
262 changed files with 5813 additions and 978 deletions

2
.gitignore vendored
View File

@@ -3,6 +3,8 @@
*~ *~
/dev_bundle /dev_bundle
/dev_bundle*.tar.gz /dev_bundle*.tar.gz
/android_bundle
/android_bundle*.tar.gz
/dist /dist
\#*\# \#*\#
.\#* .\#*

View File

@@ -20,6 +20,7 @@ don't want served to the client, such as code containing passwords or
authentication mechanisms, should be kept in the `server` directory. authentication mechanisms, should be kept in the `server` directory.
{{/note}} {{/note}}
{{> api_box isCordova}}
{{> autoApiBox "Meteor.startup"}} {{> autoApiBox "Meteor.startup"}}
@@ -601,7 +602,7 @@ your application. Each document is a EJSON object. It includes an `_id`
property whose value is unique in the collection, which Meteor will set when you property whose value is unique in the collection, which Meteor will set when you
first create the document. first create the document.
// common code on client and server declares livedata-managed mongo // common code on client and server declares a DDP-managed mongo
// collection. // collection.
Chatrooms = new Meteor.Collection("chatrooms"); Chatrooms = new Meteor.Collection("chatrooms");
Messages = new Meteor.Collection("messages"); Messages = new Meteor.Collection("messages");
@@ -1347,7 +1348,6 @@ See the [full list of
modifiers](http://www.mongodb.org/display/DOCS/Updating#Updating-ModifierOperations). modifiers](http://www.mongodb.org/display/DOCS/Updating#Updating-ModifierOperations).
{{> apiBoxTitle name="Sort Specifiers" id="sortspecifiers"}} {{> apiBoxTitle name="Sort Specifiers" id="sortspecifiers"}}
Sorts may be specified using your choice of several syntaxes: Sorts may be specified using your choice of several syntaxes:
@@ -2219,8 +2219,8 @@ any `Template.myTemplate` object.
Helpers on `Template.body` are only available in the `<body>` tags of Helpers on `Template.body` are only available in the `<body>` tags of
your app. To register a global helper, use your app. To register a global helper, use
[Template.registerHelper](#template_registerhelper). [Template.registerHelper](#template_registerhelper).
Event maps on `Template.body` don't apply to elements added to the Event maps on `Template.body` don't apply to elements added to the
body via `Blaze.render`, jQuery, or the DOM API, or to the body element body via `Blaze.render`, jQuery, or the DOM API, or to the body element
itself. To handle events on the body, window, or document, use jQuery itself. To handle events on the body, window, or document, use jQuery
or the DOM API. or the DOM API.

View File

@@ -12,6 +12,13 @@ Template.api.isServer = {
descr: ["Boolean variable. True if running in server environment."] descr: ["Boolean variable. True if running in server environment."]
}; };
Template.api.isCordova = {
id: "meteor_iscordova",
name: "Meteor.isCordova",
locus: "Anywhere",
descr: ["Boolean variable. True if running in a Cordova mobile environment."]
};
Template.api.startup = { Template.api.startup = {
id: "meteor_startup", id: "meteor_startup",
name: "Meteor.startup(func)", name: "Meteor.startup(func)",
@@ -24,6 +31,36 @@ Template.api.startup = {
] ]
}; };
Template.api.absoluteUrl = {
id: "meteor_absoluteurl",
name: "Meteor.absoluteUrl([path], [options])",
locus: "Anywhere",
descr: ["Generate an absolute URL pointing to the application. The server "
+ "reads from the `ROOT_URL` environment variable to determine "
+ "where it is running. This is taken care of automatically for "
+ "apps deployed with `meteor deploy`, but must be provided when "
+ "using `meteor build`."],
args: [
{name: "path",
type: "String",
descr: 'A path to append to the root URL. Do not include a leading "`/`".'
}
],
options: [
{name: "secure",
type: "Boolean",
descr: "Create an HTTPS URL."
},
{name: "replaceLocalhost",
type: "Boolean",
descr: "Replace localhost with 127.0.0.1. Useful for services that don't recognize localhost as a domain name."},
{name: "rootUrl",
type: "String",
descr: "Override the default ROOT_URL from the server environment. For example: \"`http://foo.example.com`\""
}
]
};
Template.api.settings = { Template.api.settings = {
id: "meteor_settings", id: "meteor_settings",
name: "Meteor.settings", name: "Meteor.settings",
@@ -1094,6 +1131,11 @@ Template.api.loginWithExternalService = {
name: "userEmail", name: "userEmail",
type: "String", type: "String",
descr: "An email address that the external service will use to pre-fill the login prompt. Currently only supported with Meteor developer accounts." descr: "An email address that the external service will use to pre-fill the login prompt. Currently only supported with Meteor developer accounts."
},
{
name: "loginStyle",
type: "String",
descr: 'Login style ("popup" or "redirect", defaults to the login service configuration). The "popup" style opens the login page in a separate popup window, which is generally preferred because the Meteor application doesn\'t need to be reloaded. The "redirect" style redirects the Meteor application\'s window to the login page, and the login service provider redirects back to the Meteor application which is then reloaded. The "redirect" style can be used in situations where a popup window can\'t be opened, such as in a mobile UIWebView. The "redirect" style however relies on session storage which isn\'t available in Safari private mode, so the "popup" style will be forced if session storage can\'t be used.'
} }
] ]
}; };

View File

@@ -176,6 +176,25 @@ Lists all the packages that you have added to your project. For each package,
lists the version that you are using. Lets you know if a newer version of that lists the version that you are using. Lets you know if a newer version of that
package is available. package is available.
<h3 id="meteoraddplatform">meteor add-platform <i>platform</i></h3>
Adds platforms to your Meteor project. You can add multiple
platforms with one command. Once a platform has been added, you
can use 'meteor run <i>platform</i>' to run on the platform, and 'meteor build'
to build the Meteor project for every added platform.
<h3 id="meteorremoveplatform">meteor remove-platform <i>platform</i></h3>
Removes a platform previously added to your Meteor project. For a
list of the platforms that your application is currently using, see
'meteor list-platforms'.
<h3 id="meteorlistplatforms">meteor list-platforms</h3>
Lists all of the platforms that have been explicitly added to your project.
<h3 id="meteormongo">meteor mongo</h3> <h3 id="meteormongo">meteor mongo</h3>
@@ -206,16 +225,25 @@ running. Quit all running meteor applications before running this.
{{/warning}} {{/warning}}
<h3 id="meteorbundle">meteor bundle</h3> <h3 id="meteorbuild">meteor build</h3>
Package the application up for deployment. The output is a tarball Package this project up for deployment. The output is a directory with several
that includes everything necessary to run the build artifacts:
application. See `README` in the tarball for details.
You can use this to host a Meteor application on your own server, <ul><li>a tarball that includes everything necessary to run the application server
instead of deploying to Meteor's servers. You will have to deal with (see `README` in the tarball for details)</li>
logging, monitoring, backups, load-balancing, etc, all of which we <li>an unassigned `apk` bundle and a project source if Android is targetted as a
handle for you if you use `meteor deploy`. mobile platform</li>
<li>a directory with an Xcode project source if iOS is targetted as a mobile
platform</li></ul>
You can use the application server bundle to host a Meteor application on your
own server, instead of deploying to Meteor's servers. You will have to deal
with logging, monitoring, backups, load-balancing, etc, all of which we handle
for you if you use `meteor deploy`.
Unassigned `apk` bundle and the outputted Xcode project can be used to deploy
your mobile apps to Android Play Store and Apple App Store.
By default, your application is bundled for your current architecture. By default, your application is bundled for your current architecture.
This may cause difficulties if your app contains binary code due to, This may cause difficulties if your app contains binary code due to,

File diff suppressed because one or more lines are too long

View File

@@ -24,7 +24,7 @@ caching simply add the `appcache` package to your project.
(Note however that the `appcache` package by itself doesn't make (Note however that the `appcache` package by itself doesn't make
*data* available offline: in an application loaded offline, a Meteor *data* available offline: in an application loaded offline, a Meteor
Collection will appear to be empty in the client until the Internet Collection will appear to be empty in the client until the Internet
becomes available and the browser is able to establish a livedata becomes available and the browser is able to establish a DDP
connection). connection).
The application cache works transparently in all supported browsers The application cache works transparently in all supported browsers

2
meteor
View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
BUNDLE_VERSION=0.3.51 BUNDLE_VERSION=0.3.50
# OS Check. Put here because here is where we download the precompiled # OS Check. Put here because here is where we download the precompiled
# bundles that are arch specific. # bundles that are arch specific.

View File

@@ -292,6 +292,46 @@ Accounts.loginServicesConfigured = function () {
return loginServicesHandle.ready(); return loginServicesHandle.ready();
}; };
// Some login services such as the redirect login flow or the resume
// login handler can log the user in at page load time. The
// Meteor.loginWithX functions have a callback argument, but the
// callback function instance won't be in memory any longer if the
// page was reloaded. The `onPageLoadLogin` function allows a
// callback to be registered for the case where the login was
// initiated in a previous VM, and we now have the result of the login
// attempt in a new VM.
var pageLoadLoginCallbacks = [];
var pageLoadLoginAttemptInfo = null;
// Register a callback to be called if we have information about a
// login attempt at page load time. Call the callback immediately if
// we already have the page load login attempt info, otherwise stash
// the callback to be called if and when we do get the attempt info.
//
Accounts.onPageLoadLogin = function (f) {
if (pageLoadLoginAttemptInfo)
f(pageLoadLoginAttemptInfo);
else
pageLoadLoginCallbacks.push(f);
};
// Receive the information about the login attempt at page load time.
// Call registered callbacks, and also record the info in case
// someone's callback hasn't been registered yet.
//
Accounts._pageLoadLogin = function (attemptInfo) {
if (pageLoadLoginAttemptInfo) {
Meteor._debug("Ignoring unexpected duplicate page load login attempt info");
return;
}
_.each(pageLoadLoginCallbacks, function (callback) { callback(attemptInfo); });
pageLoadLoginCallbacks = [];
pageLoadLoginAttemptInfo = attemptInfo;
};
/// ///
/// HANDLEBARS HELPERS /// HANDLEBARS HELPERS
/// ///

View File

@@ -100,6 +100,16 @@ if (autoLoginEnabled) {
Meteor._debug("Error logging in with token: " + err); Meteor._debug("Error logging in with token: " + err);
makeClientLoggedOut(); makeClientLoggedOut();
} }
Accounts._pageLoadLogin({
type: "resume",
allowed: !err,
error: err,
methodName: "login",
// XXX This is duplicate code with loginWithToken, but
// loginWithToken can also be called at other times besides
// page load.
methodArguments: [{resume: token}]
});
}); });
} }
} }

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "A user account system", summary: "A user account system",
version: "1.0.0" version: "1.0.1-rc0"
}); });
Package.on_use(function (api) { Package.on_use(function (api) {
@@ -17,11 +17,11 @@ Package.on_use(function (api) {
api.use('service-configuration', ['client', 'server'], { unordered: true }); api.use('service-configuration', ['client', 'server'], { unordered: true });
// needed for getting the currently logged-in user // needed for getting the currently logged-in user
api.use('livedata', ['client', 'server']); api.use('ddp', ['client', 'server']);
// need this because of the Meteor.users collection but in the future // need this because of the Meteor.users collection but in the future
// we'd probably want to abstract this away // we'd probably want to abstract this away
api.use('mongo-livedata', ['client', 'server']); api.use('mongo', ['client', 'server']);
// If the 'blaze' package is loaded, we'll define some helpers like // If the 'blaze' package is loaded, we'll define some helpers like
// {{currentUser}}. If not, no biggie. // {{currentUser}}. If not, no biggie.

View File

@@ -1,3 +1,52 @@
// Allow server to specify a specify subclass of errors. We should come
// up with a more generic way to do this!
var convertError = function (err) {
if (err && err instanceof Meteor.Error &&
err.error === Accounts.LoginCancelledError.numericError)
return new Accounts.LoginCancelledError(err.reason);
else
return err;
};
// For the redirect login flow, the final step is that we're
// redirected back to the application. The credentialToken for this
// login attempt is stored in the reload migration data, and the
// credentialSecret for a successful login is stored in session
// storage.
Meteor.startup(function () {
var oauth = OAuth.getDataAfterRedirect();
if (! oauth)
return;
// We'll only have the credentialSecret if the login completed
// successfully. However we still call the login method anyway to
// retrieve the error if the login was unsuccessful.
var methodName = 'login';
var methodArguments = [{oauth: _.pick(oauth, 'credentialToken', 'credentialSecret')}];
Accounts.callLoginMethod({
methodArguments: methodArguments,
userCallback: function (err) {
// The redirect login flow is complete. Construct an
// `attemptInfo` object with the login result, and report back
// to the code which initiated the login attempt
// (e.g. accounts-ui, when that package is being used).
err = convertError(err);
Accounts._pageLoadLogin({
type: oauth.loginService,
allowed: !err,
error: err,
methodName: methodName,
methodArguments: methodArguments
});
}
});
});
// Send an OAuth login method to the server. If the user authorized // Send an OAuth login method to the server. If the user authorized
// access in the popup this should log the user in, otherwise // access in the popup this should log the user in, otherwise
// nothing should happen. // nothing should happen.
@@ -9,14 +58,7 @@ Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback) {
credentialSecret: credentialSecret credentialSecret: credentialSecret
}}], }}],
userCallback: callback && function (err) { userCallback: callback && function (err) {
// Allow server to specify a specify subclass of errors. We should come callback(convertError(err));
// up with a more generic way to do this!
if (err && err instanceof Meteor.Error &&
err.error === Accounts.LoginCancelledError.numericError) {
callback(new Accounts.LoginCancelledError(err.reason));
} else {
callback(err);
}
}}); }});
}; };

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Password support for accounts", summary: "Password support for accounts",
version: "1.0.0" version: "1.0.1-rc0"
}); });
Package.on_use(function(api) { Package.on_use(function(api) {
@@ -15,7 +15,7 @@ Package.on_use(function(api) {
api.use('random', ['server']); api.use('random', ['server']);
api.use('check'); api.use('check');
api.use('underscore'); api.use('underscore');
api.use('livedata', ['client', 'server']); api.use('ddp', ['client', 'server']);
api.add_files('email_templates.js', 'server'); api.add_files('email_templates.js', 'server');
api.add_files('password_server.js', 'server'); api.add_files('password_server.js', 'server');
@@ -25,7 +25,7 @@ Package.on_use(function(api) {
Package.on_test(function(api) { Package.on_test(function(api) {
api.use(['accounts-password', 'tinytest', 'test-helpers', 'tracker', api.use(['accounts-password', 'tinytest', 'test-helpers', 'tracker',
'accounts-base', 'random', 'email', 'underscore', 'check', 'accounts-base', 'random', 'email', 'underscore', 'check',
'livedata']); 'ddp']);
api.add_files('password_tests_setup.js', 'server'); api.add_files('password_tests_setup.js', 'server');
api.add_files('password_tests.js', ['client', 'server']); api.add_files('password_tests.js', ['client', 'server']);
api.add_files('email_tests_setup.js', 'server'); api.add_files('email_tests_setup.js', 'server');

View File

@@ -103,6 +103,16 @@
{{/each}} {{/each}}
</table> </table>
</p> </p>
<p class="new-section">
Choose the login style:
</p>
<p>
&emsp;<input id="configure-login-service-dialog-popupBasedLogin" type="radio" checked="checked" name="loginStyle" value="popup">
<label for="configure-login-service-dialog-popupBasedLogin">Popup-based login (recommended for most applications)</label>
<br>&emsp;<input id="configure-login-service-dialog-redirectBasedLogin" type="radio" name="loginStyle" value="redirect">
<label for="configure-login-service-dialog-redirectBasedLogin">Redirect-based login (special cases such as using a UIWebView)</label>
</p>
<div class="new-section"> <div class="new-section">
<div class="login-button configure-login-service-dismiss-button"> <div class="login-button configure-login-service-dismiss-button">
I'll do this later I'll do this later

View File

@@ -189,6 +189,10 @@ Template._configureLoginServiceDialog.events({
.replace(/^\s*|\s*$/g, ""); // trim() doesnt work on IE8; .replace(/^\s*|\s*$/g, ""); // trim() doesnt work on IE8;
}); });
configuration.loginStyle =
$('#configure-login-service-dialog input[name="loginStyle"]:checked')
.val();
// Configure this login service // Configure this login service
Accounts.connection.call( Accounts.connection.call(
"configureLoginService", configuration, function (error, result) { "configureLoginService", configuration, function (error, result) {

View File

@@ -1,21 +1,37 @@
// for convenience // for convenience
var loginButtonsSession = Accounts._loginButtonsSession; var loginButtonsSession = Accounts._loginButtonsSession;
var loginResultCallback = function (serviceName, err) {
if (!err) {
loginButtonsSession.closeDropdown();
} else if (err instanceof Accounts.LoginCancelledError) {
// do nothing
} else if (err instanceof ServiceConfiguration.ConfigError) {
loginButtonsSession.configureService(serviceName);
} else {
loginButtonsSession.errorMessage(err.reason || "Unknown error");
}
};
// In the login redirect flow, we'll have the result of the login
// attempt at page load time when we're redirected back to the
// application. Register a callback to update the UI (i.e. to close
// the dialog on a successful login or display the error on a failed
// login).
//
Accounts.onPageLoadLogin(function (attemptInfo) {
// Ignore if we have a left over login attempt for a service that is no longer registered.
if (_.contains(_.pluck(getLoginServices(), "name"), attemptInfo.type))
loginResultCallback(attemptInfo.type, attemptInfo.err);
});
Template._loginButtonsLoggedOutSingleLoginButton.events({ Template._loginButtonsLoggedOutSingleLoginButton.events({
'click .login-button': function () { 'click .login-button': function () {
var serviceName = this.name; var serviceName = this.name;
loginButtonsSession.resetMessages(); loginButtonsSession.resetMessages();
var callback = function (err) {
if (!err) {
loginButtonsSession.closeDropdown();
} else if (err instanceof Accounts.LoginCancelledError) {
// do nothing
} else if (err instanceof ServiceConfiguration.ConfigError) {
loginButtonsSession.configureService(serviceName);
} else {
loginButtonsSession.errorMessage(err.reason || "Unknown error");
}
};
// XXX Service providers should be able to specify their // XXX Service providers should be able to specify their
// `Meteor.loginWithX` method name. // `Meteor.loginWithX` method name.
@@ -32,7 +48,9 @@ Template._loginButtonsLoggedOutSingleLoginButton.events({
if (Accounts.ui._options.forceApprovalPrompt[serviceName]) if (Accounts.ui._options.forceApprovalPrompt[serviceName])
options.forceApprovalPrompt = Accounts.ui._options.forceApprovalPrompt[serviceName]; options.forceApprovalPrompt = Accounts.ui._options.forceApprovalPrompt[serviceName];
loginWithService(options, callback); loginWithService(options, function (err) {
loginResultCallback(serviceName, err);
});
} }
}); });

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Unstyled version of login widgets", summary: "Unstyled version of login widgets",
version: "1.0.0" version: "1.0.1-rc0"
}); });
Package.on_use(function (api) { Package.on_use(function (api) {

View File

@@ -265,7 +265,7 @@
margin-top: 7px; margin-top: 7px;
margin-bottom: -2px; margin-bottom: -2px;
} }
input { input[type=text] {
// Be pixel-accurate in IE 8+ regardless of our borders and // Be pixel-accurate in IE 8+ regardless of our borders and
// paddings, at the expense of IE 7. // paddings, at the expense of IE 7.
// Any heights or widths applied to this element will set the // Any heights or widths applied to this element will set the
@@ -356,7 +356,7 @@
margin-top: -220px; /* = approximately -height/2, though height can change */ margin-top: -220px; /* = approximately -height/2, though height can change */
table { width: 100%; } table { width: 100%; }
input { input[type=text] {
width: 100%; width: 100%;
font-family: "Courier New", Courier, monospace; font-family: "Courier New", Courier, monospace;
} }

View File

@@ -89,7 +89,7 @@ WebApp.connectHandlers.use(function(req, res, next) {
manifest += "CACHE:" + "\n"; manifest += "CACHE:" + "\n";
manifest += "/" + "\n"; manifest += "/" + "\n";
_.each(WebApp.clientProgram.manifest, function (resource) { _.each(WebApp.clientPrograms[WebApp.defaultArch].manifest, function (resource) {
if (resource.where === 'client' && if (resource.where === 'client' &&
! RoutePolicy.classify(resource.url)) { ! RoutePolicy.classify(resource.url)) {
manifest += resource.url; manifest += resource.url;
@@ -118,7 +118,7 @@ WebApp.connectHandlers.use(function(req, res, next) {
// request to the server and have the asset served from cache by // request to the server and have the asset served from cache by
// specifying the full URL with hash in their code (manually, with // specifying the full URL with hash in their code (manually, with
// some sort of URL rewriting helper) // some sort of URL rewriting helper)
_.each(WebApp.clientProgram.manifest, function (resource) { _.each(WebApp.clientPrograms[WebApp.defaultArch].manifest, function (resource) {
if (resource.where === 'client' && if (resource.where === 'client' &&
! RoutePolicy.classify(resource.url) && ! RoutePolicy.classify(resource.url) &&
!resource.cacheable) { !resource.cacheable) {
@@ -154,7 +154,7 @@ WebApp.connectHandlers.use(function(req, res, next) {
var sizeCheck = function() { var sizeCheck = function() {
var totalSize = 0; var totalSize = 0;
_.each(WebApp.clientProgram.manifest, function (resource) { _.each(WebApp.clientPrograms[WebApp.defaultArch].manifest, function (resource) {
if (resource.where === 'client' && if (resource.where === 'client' &&
! RoutePolicy.classify(resource.url)) { ! RoutePolicy.classify(resource.url)) {
totalSize += resource.size; totalSize += resource.size;

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Enable the application cache in the browser", summary: "Enable the application cache in the browser",
version: "1.0.0" version: "1.0.0-cordova1"
}); });
Package.on_use(function (api) { Package.on_use(function (api) {

View File

@@ -4,8 +4,8 @@ Package.describe({
}); });
Package.on_use(function (api) { Package.on_use(function (api) {
api.use(['logging', 'underscore', 'livedata', 'ejson', 'follower-livedata']); api.use(['logging', 'underscore', 'ddp', 'ejson', 'follower-livedata']);
api.use(['mongo-livedata'], { api.use(['mongo'], {
unordered: true unordered: true
}); });
api.add_files(['config.js'], 'server'); api.add_files(['config.js'], 'server');

View File

@@ -0,0 +1,160 @@
var autoupdateVersionCordova = __meteor_runtime_config__.autoupdateVersionCordova || "unknown";
// The collection of acceptable client versions.
ClientVersions = new Meteor.Collection("meteor_autoupdate_clientVersions");
Autoupdate = {};
Autoupdate.newClientAvailable = function () {
return !! ClientVersions.findOne({
_id: 'version-cordova',
version: {$ne: autoupdateVersionCordova}
});
};
var writeFile = function (directoryPath, fileName, content, cb) {
var fail = function (err) {
cb(new Error("Failed to write file: ", err), null);
};
window.resolveLocalFileSystemURL(directoryPath,
function (dirEntry) {
var success = function (fileEntry) {
fileEntry.createWriter(function (writer) {
writer.onwrite = function (evt) {
var result = evt.target.result;
cb(null, result);
};
writer.onerror = fail;
writer.write(content);
}, fail);
};
dirEntry.getFile(fileName, { create: true, exclusive: false },
success, fail);
}, fail);
};
var hasCalledReload = false;
var onNewVersion = function () {
var ft = new FileTransfer();
var urlPrefix = Meteor.absoluteUrl() + 'cordova';
var localPathPrefix = cordova.file.applicationStorageDirectory +
'Documents/meteor/';
HTTP.get(urlPrefix + '/manifest.json', function (err, res) {
if (err || ! res.data) {
console.log('failed to download the manifest ' + (err && err.message) + ' ' + (res && res.content));
return;
}
var program = res.data;
var manifest = program.manifest;
var version = program.version;
var ft = new FileTransfer();
var downloads = 0;
_.each(manifest, function (item) {
if (item.url) downloads++;
});
var versionPrefix = localPathPrefix + version;
var afterAllFilesDownloaded = _.after(downloads, function () {
writeFile(versionPrefix, 'manifest.json',
JSON.stringify(program, undefined, 2),
function (err) {
if (err) {
console.log("Failed to write manifest.json");
// XXX do something smarter?
return;
}
// success! downloaded all sources and saved the manifest
// save the version string for atomicity
writeFile(localPathPrefix, 'version', version,
function (err) {
if (err) {
console.log("Failed to write version");
return;
}
// don't call reload twice!
if (! hasCalledReload) {
Package.reload.Reload._reload();
}
});
});
});
_.each(manifest, function (item) {
if (! item.url) return;
// Add a cache buster to ensure that we don't cache an old asset.
var uri = encodeURI(urlPrefix + item.url + '?' + Random.id());
// Try to dowload the file a few times.
var tries = 0;
var tryDownload = function () {
ft.download(uri, versionPrefix + item.url, function (entry) {
if (entry) {
afterAllFilesDownloaded();
}
}, function (err) {
// It failed, try again if we have tried less than 5 times.
if (tries++ < 5) {
tryDownload();
} else {
console.log('fail source: ', error.source);
console.log('fail target: ', error.target);
}
});
};
tryDownload();
});
});
};
var retry = new Retry({
minCount: 0, // don't do any immediate retries
baseTimeout: 30*1000 // start with 30s
});
var failures = 0;
Autoupdate._retrySubscription = function () {
Meteor.subscribe("meteor_autoupdate_clientVersions", {
onError: function (error) {
Meteor._debug("autoupdate subscription failed:", error);
failures++;
retry.retryLater(failures, function () {
// Just retry making the subscription, don't reload the whole
// page. While reloading would catch more cases (for example,
// the server went back a version and is now doing old-style hot
// code push), it would also be more prone to reload loops,
// which look really bad to the user. Just retrying the
// subscription over DDP means it is at least possible to fix by
// updating the server.
Autoupdate._retrySubscription();
});
}
});
if (Package.reload) {
var checkNewVersionDocument = function (doc) {
var self = this;
if (doc.version !== autoupdateVersionCordova) {
onNewVersion();
}
};
var handle = ClientVersions.find({
_id: 'version-cordova'
}).observe({
added: checkNewVersionDocument,
changed: checkNewVersionDocument
});
}
};
Meteor.startup(Autoupdate._retrySubscription);

View File

@@ -51,6 +51,7 @@ ClientVersions = new Meteor.Collection("meteor_autoupdate_clientVersions",
// startup. // startup.
Autoupdate.autoupdateVersion = null; Autoupdate.autoupdateVersion = null;
Autoupdate.autoupdateVersionRefreshable = null; Autoupdate.autoupdateVersionRefreshable = null;
Autoupdate.autoupdateVersionCordova = null;
var syncQueue = new Meteor._SynchronousQueue(); var syncQueue = new Meteor._SynchronousQueue();
@@ -59,7 +60,7 @@ var updateVersions = function (shouldReloadClientProgram) {
// Step 1: load the current client program on the server and update the // Step 1: load the current client program on the server and update the
// hash values in __meteor_runtime_config__. // hash values in __meteor_runtime_config__.
if (shouldReloadClientProgram) { if (shouldReloadClientProgram) {
WebAppInternals.reloadClientProgram(); WebAppInternals.reloadClientPrograms();
} }
// If we just re-read the client program, or if we don't have an autoupdate // If we just re-read the client program, or if we don't have an autoupdate
@@ -81,6 +82,12 @@ var updateVersions = function (shouldReloadClientProgram) {
process.env.SERVER_ID || // XXX COMPAT 0.6.6 process.env.SERVER_ID || // XXX COMPAT 0.6.6
WebApp.calculateClientHashRefreshable(); WebApp.calculateClientHashRefreshable();
Autoupdate.autoupdateVersionCordova =
__meteor_runtime_config__.autoupdateVersionCordova =
process.env.AUTOUPDATE_VERSION ||
process.env.SERVER_ID || // XXX COMPAT 0.6.6
WebApp.calculateClientHashCordova();
// Step 2: form the new client boilerplate which contains the updated // Step 2: form the new client boilerplate which contains the updated
// assets and __meteor_runtime_config__. // assets and __meteor_runtime_config__.
if (shouldReloadClientProgram) { if (shouldReloadClientProgram) {
@@ -116,6 +123,18 @@ var updateVersions = function (shouldReloadClientProgram) {
ClientVersions.update("version-refreshable", { $set: { ClientVersions.update("version-refreshable", { $set: {
version: Autoupdate.autoupdateVersionRefreshable, version: Autoupdate.autoupdateVersionRefreshable,
assets: WebAppInternals.refreshableAssets assets: WebAppInternals.refreshableAssets
}});
}
if (! ClientVersions.findOne({_id: "version-cordova"})) {
ClientVersions.insert({
_id: "version-cordova",
version: Autoupdate.autoupdateVersionCordova,
refreshable: false
});
} else {
ClientVersions.update("version-cordova", { $set: {
version: Autoupdate.autoupdateVersionCordova
}}); }});
} }
}; };

View File

@@ -1,16 +1,23 @@
Package.describe({ Package.describe({
summary: "Update the client when new client code is available", summary: "Update the client when new client code is available",
version: '1.0.5' version: '1.0.7-cordova6'
});
Cordova.depends({
'org.apache.cordova.file': '1.3.0',
'org.apache.cordova.file-transfer': '0.4.4'
}); });
Package.on_use(function (api) { Package.on_use(function (api) {
api.use('webapp', 'server'); api.use('webapp', 'server');
api.use(['tracker', 'retry'], 'client'); api.use(['tracker', 'retry'], 'client');
api.use(['livedata', 'mongo-livedata', 'underscore'], ['client', 'server']); api.use(['ddp', 'mongo', 'underscore'], ['client', 'server']);
api.use('tracker', 'client'); api.use('tracker', 'client');
api.use('reload', 'client', {weak: true}); api.use('reload', 'client', {weak: true});
api.use('http', 'web.cordova');
api.export('Autoupdate'); api.export('Autoupdate');
api.add_files('autoupdate_server.js', 'server'); api.add_files('autoupdate_server.js', 'server');
api.add_files('autoupdate_client.js', 'client'); api.add_files('autoupdate_client.js', 'web.browser');
api.add_files('autoupdate_cordova.js', 'web.cordova');
}); });

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Meteor Reactive Templating library", summary: "Meteor Reactive Templating library",
version: '1.0.3' version: '2.0.0-rc0'
}); });
Package.on_use(function (api) { Package.on_use(function (api) {

View File

@@ -0,0 +1 @@
.build*

View File

@@ -0,0 +1,102 @@
var fs = Npm.require('fs');
var Future = Npm.require('fibers/future');
var path = Npm.require('path');
// Copied from webapp_server
var readUtf8FileSync = function (filename) {
return Future.wrap(fs.readFile)(filename, 'utf8').wait();
};
Boilerplate = function (arch, manifest, options) {
var self = this;
options = options || {};
self.template = _getTemplate(arch);
self.baseData = null;
self.func = null;
self._generateBoilerplateFromManifestAndSource(
manifest,
self.template,
options
);
};
Boilerplate.prototype.toHTML = function () {
var self = this;
if (! self.baseData || ! self.func)
throw new Error('Boilerplate did not instantiate correctly.');
return "<!DOCTYPE html>\n" +
Blaze.toHTML(Blaze.With(self.baseData,
self.func));
};
// XXX Exported to allow client-side only changes to rebuild the boilerplate
// without requiring a full server restart.
// Produces an HTML string with given manifest and boilerplateSource.
// Optionally takes urlMapper in case urls from manifest need to be prefixed
// or rewritten.
// Optionally takes pathMapper for resolving relative file system paths.
// Optionally allows to override fields of the data context.
Boilerplate.prototype._generateBoilerplateFromManifestAndSource =
function (manifest, boilerplateSource, options) {
var self = this;
// map to the identity by default
var urlMapper = options.urlMapper || _.identity;
var pathMapper = options.pathMapper || _.identity;
var boilerplateBaseData = {
css: [],
js: [],
head: '',
body: '',
meteorManifest: JSON.stringify(manifest)
};
// allow the caller to extend the default base data
_.extend(boilerplateBaseData, options.baseDataExtension);
_.each(manifest, function (item) {
var urlPath = urlMapper(item.url);
var itemObj = { url: urlPath };
if (options.inline) {
itemObj.scriptContent = readUtf8FileSync(
pathMapper(item.path));
itemObj.inline = true;
}
if (item.type === 'css' && item.where === 'client') {
boilerplateBaseData.css.push(itemObj);
}
if (item.type === 'js' && item.where === 'client') {
boilerplateBaseData.js.push(itemObj);
}
if (item.type === 'head') {
boilerplateBaseData.head =
readUtf8FileSync(pathMapper(item.path));
}
if (item.type === 'body') {
boilerplateBaseData.body =
readUtf8FileSync(pathMapper(item.path));
}
});
var boilerplateRenderCode = SpacebarsCompiler.compile(
boilerplateSource, { isBody: true });
// Note that we are actually depending on eval's local environment capture
// so that UI and HTML are visible to the eval'd code.
// XXX the template we are evaluating relies on the fact that UI is globally
// available.
global.UI = UI;
self.func = eval(boilerplateRenderCode);
self.baseData = boilerplateBaseData;
};
var _getTemplate = _.memoize(function (arch) {
var filename = 'boilerplate_' + arch + '.html';
return Assets.getText(filename);
});

View File

@@ -0,0 +1,29 @@
<html>
<head>
<meta charset="utf-8">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi">
<meta name="msapplication-tap-highlight" content="no">
<script type='text/javascript'>
__meteor_runtime_config__ = {{meteorRuntimeConfig}};
if (/Android/i.test(navigator.userAgent)) {
// When Android app is emulated, it cannot connect to localhost,
// instead it should connect to 10.0.2.2
__meteor_runtime_config__.ROOT_URL = (__meteor_runtime_config__.ROOT_URL || '').replace(/localhost/i, '10.0.2.2');
__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL || '').replace(/localhost/i, '10.0.2.2');
}
__meteor_manifest__ = {{meteorManifest}};
</script>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="meteor_cordova_loader.js"></script>
{{{head}}}
</head>
<body>
{{{body}}}
</body>
</html>

View File

@@ -0,0 +1,19 @@
Package.describe({
summary: "Generates the boilerplate html from program's manifest",
version: '1.0.0-cordova1'
});
Package.on_use(function (api) {
api.use(['underscore', 'spacebars-compiler',
'spacebars', 'htmljs', 'ui'], 'server');
api.add_files(['boilerplate-generator.js'], 'server');
api.export(['Boilerplate'], 'server');
// These are spacebars templates, but we process them manually with the
// spacebars compiler rather than letting the 'templating' package (which
// isn't fully supported on the server yet) handle it. That also means that
// they don't contain the outer "<template>" tag.
api.add_files(['boilerplate_web.browser.html',
'boilerplate_web.cordova.html'],
'server', {isAsset: true});
});

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Front-end framework from Twitter", summary: "Front-end framework from Twitter",
version: "1.0.0" version: "1.0.1-rc0"
}); });
Package.on_use(function (api) { Package.on_use(function (api) {

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Javascript dialect with fewer braces and semicolons", summary: "Javascript dialect with fewer braces and semicolons",
version: "1.0.2" version: "1.0.2-cordova4"
}); });
Package._transitional_registerBuildPlugin({ Package._transitional_registerBuildPlugin({

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Given the set of the constraints, picks a satisfying configuration", summary: "Given the set of the constraints, picks a satisfying configuration",
version: "1.0.10" version: "1.0.12-cordova6"
}); });
Npm.depends({ Npm.depends({

View File

@@ -6,7 +6,7 @@ Package.describe({
Npm.depends({optimist: '0.6.0'}); Npm.depends({optimist: '0.6.0'});
Package.on_use(function (api) { Package.on_use(function (api) {
api.use(['logging', 'underscore', 'livedata', 'mongo-livedata', 'follower-livedata', 'application-configuration'], 'server'); api.use(['logging', 'underscore', 'ddp', 'mongo', 'follower-livedata', 'application-configuration'], 'server');
api.export('Ctl', 'server'); api.export('Ctl', 'server');
api.add_files('ctl-helper.js', 'server'); api.add_files('ctl-helper.js', 'server');
}); });

View File

@@ -4,7 +4,7 @@ Package.describe({
}); });
Package.on_use(function (api) { Package.on_use(function (api) {
api.use(['underscore', 'livedata', 'mongo-livedata', 'ctl-helper', 'application-configuration', 'follower-livedata'], 'server'); api.use(['underscore', 'ddp', 'mongo', 'ctl-helper', 'application-configuration', 'follower-livedata'], 'server');
api.export('main', 'server'); api.export('main', 'server');
api.add_files('ctl.js', 'server'); api.add_files('ctl.js', 'server');
}); });

1
packages/ddp/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.build*

View File

@@ -7,7 +7,7 @@ DDP is a protocol between a client and a server that supports two operations:
client informed about the contents of those documents as they change over client informed about the contents of those documents as they change over
time. time.
This document specifies the version "pre2" of DDP. It's a rough description of This document specifies the version "1" of DDP. It's a rough description of
the protocol and not intended to be entirely definitive. the protocol and not intended to be entirely definitive.
## General Message Structure: ## General Message Structure:
@@ -21,6 +21,14 @@ DDP messages are JSON objects, with some fields specified to be EJSON. Each one
has a `msg` field that specifies the message type, as well as other fields has a `msg` field that specifies the message type, as well as other fields
depending on message type. depending on message type.
The client and the server must ignore any unknown fields in messages. Future
minor revisions of DDP might add extra fields without changing the DDP version;
the client must therefore silently ignore unknown fields. However, the client
must not send extra fields other than those documented in the DDP protocol, in
case these extra fields have meaning to future servers. On the server, all
field changes must be optional/ignorable for compatability with older clients;
otherwise a new protocol version would be required.
## Establishing a DDP Connection: ## Establishing a DDP Connection:
### Messages: ### Messages:
@@ -324,3 +332,14 @@ behaves differently to the server, then syncing will fix this.
* If both client and server support randomSeed, in the normal case the ids * If both client and server support randomSeed, in the normal case the ids
generated will be the same, and syncing will be a no-op. generated will be the same, and syncing will be a no-op.
## Version History
```pre1``` was the first version of DDP
```pre2``` added keep-alive (ping & pong messages), and randomSeed.
```1``` should be considered the first official version of DDP. It is
currently identical to pre2, although non-incompatible changes may be made to
it in future.

View File

@@ -1,4 +1,6 @@
SUPPORTED_DDP_VERSIONS = [ 'pre2', 'pre1' ]; // All the supported versions (for both the client and server)
// These must be in order of preference; most favored-first
SUPPORTED_DDP_VERSIONS = [ '1', 'pre2', 'pre1' ];
LivedataTest.SUPPORTED_DDP_VERSIONS = SUPPORTED_DDP_VERSIONS; LivedataTest.SUPPORTED_DDP_VERSIONS = SUPPORTED_DDP_VERSIONS;

View File

@@ -1407,6 +1407,40 @@ Tinytest.add("livedata connection - ping with id", function (test) {
testGotMessage(test, stream, {msg: 'pong', id: id}); testGotMessage(test, stream, {msg: 'pong', id: id});
}); });
_.each(LivedataTest.SUPPORTED_DDP_VERSIONS, function (version) {
Tinytest.addAsync("livedata connection - ping from " + version,
function (test, onComplete) {
var connection = new LivedataTest.Connection(getSelfConnectionUrl(), {
reloadWithOutstanding: true,
supportedDDPVersions: [version],
onDDPVersionNegotiationFailure: function () { test.fail(); onComplete(); },
onConnected: function () {
test.equal(connection._version, version);
// It's a little naughty to access _stream and _send, but it works...
connection._stream.on('message', function (json) {
var msg = JSON.parse(json);
var done = false;
if (msg.msg === 'pong') {
test.notEqual(version, "pre1");
done = true;
} else if (msg.msg === 'error') {
// Version pre1 does not play ping-pong
test.equal(version, "pre1");
done = true;
} else {
Meteor._debug("Got unexpected message: " + json);
}
if (done) {
connection._stream.disconnect({_permanent: true});
onComplete();
}
});
connection._send({msg: 'ping'});
}
});
});
});
var getSelfConnectionUrl = function () { var getSelfConnectionUrl = function () {
if (Meteor.isClient) { if (Meteor.isClient) {
return Meteor._relativeToSiteRootUrl("/"); return Meteor._relativeToSiteRootUrl("/");

98
packages/ddp/package.js Normal file
View File

@@ -0,0 +1,98 @@
Package.describe({
summary: "Meteor's latency-compensated distributed data framework",
version: '1.0.7'
});
// We use 'faye-websocket' for connections in server-to-server DDP, mostly
// because it's the same library used as a server in sockjs, and it's easiest to
// deal with a single websocket implementation. (Plus, its maintainer is easy
// to work with on pull requests.)
//
// (By listing faye-websocket first, it's more likely that npm deduplication
// will prevent a second copy of faye-websocket from being installed inside
// sockjs.)
Npm.depends({
// A fork fixing https://github.com/faye/websocket-driver-node/pull/8 (ie
// "open from inactive client" errors). Note that sockjs won't use this fork,
// but the bug only affects the websocket client, not the server.
"faye-websocket": "https://github.com/meteor/faye-websocket-node/tarball/ccc180998b1396093c24d0df7ebc1d199c276552",
sockjs: "0.3.9"
});
Package.on_use(function (api) {
api.use(['check', 'random', 'ejson', 'json', 'underscore', 'tracker',
'logging', 'retry'],
['client', 'server']);
// It is OK to use this package on a server architecture without making a
// server (in order to do server-to-server DDP as a client). So these are only
// included as weak dependencies.
// XXX split this package into multiple packages or multiple slices instead
api.use(['webapp', 'routepolicy'], 'server', {weak: true});
// Detect whether or not the user wants us to audit argument checks.
api.use(['audit-argument-checks'], 'server', {weak: true});
// Allow us to detect 'autopublish', so we can print a warning if the user
// runs Meteor.publish while it's loaded.
api.use('autopublish', 'server', {weak: true});
// If the facts package is loaded, publish some statistics.
api.use('facts', 'server', {weak: true});
api.use('callback-hook', 'server');
api.export('DDP');
api.export('DDPServer', 'server');
api.export('LivedataTest', {testOnly: true});
// Transport
api.use('reload', 'client', {weak: true});
api.add_files('common.js');
api.add_files(['sockjs-0.3.4.js', 'stream_client_sockjs.js'], 'client');
api.add_files('stream_client_nodejs.js', 'server');
api.add_files('stream_client_common.js', ['client', 'server']);
api.add_files('stream_server.js', 'server');
// we depend on LocalCollection._diffObjects, _applyChanges,
// _idParse, _idStringify.
api.use('minimongo', ['client', 'server']);
api.add_files('heartbeat.js', ['client', 'server']);
api.add_files('livedata_server.js', 'server');
api.add_files('writefence.js', 'server');
api.add_files('crossbar.js', 'server');
api.add_files('livedata_common.js', ['client', 'server']);
api.add_files('random_stream.js', ['client', 'server']);
api.add_files('livedata_connection.js', ['client', 'server']);
api.add_files('client_convenience.js', 'client');
api.add_files('server_convenience.js', 'server');
});
Package.on_test(function (api) {
api.use('livedata', ['client', 'server']);
api.use('mongo', ['client', 'server']);
api.use('test-helpers', ['client', 'server']);
api.use(['underscore', 'tinytest', 'random', 'tracker', 'minimongo', 'reactive-var']);
api.add_files('stub_stream.js');
api.add_files('livedata_server_tests.js', 'server');
api.add_files('livedata_connection_tests.js', ['client', 'server']);
api.add_files('livedata_tests.js', ['client', 'server']);
api.add_files('livedata_test_service.js', ['client', 'server']);
api.add_files('session_view_tests.js', ['server']);
api.add_files('crossbar_tests.js', ['server']);
api.add_files('random_stream_tests.js', ['client', 'server']);
api.use('http', 'client');
api.add_files(['stream_tests.js'], 'client');
api.add_files('stream_client_tests.js', 'server');
api.use('check', ['client', 'server']);
});

View File

@@ -1 +0,0 @@
Deps = Tracker;

View File

@@ -2,11 +2,10 @@
Package.describe({ Package.describe({
summary: "Deprecated package, please use tracker instead.", summary: "Deprecated package, please use tracker instead.",
version: '1.0.1' version: '1.0.2-rc1'
}); });
Package.on_use(function (api) { Package.on_use(function (api) {
api.use('tracker'); api.use('tracker');
api.export('Deps'); api.imply('tracker');
api.add_files('deps.js');
}); });

View File

@@ -0,0 +1 @@
.build*

View File

@@ -0,0 +1,26 @@
var headingDep = new Deps.Dependency();
var heading = null;
var headingRefresh = false;
var callback = function (newHeading) {
heading = newHeading;
headingDep.changed();
};
var throttled = _.throttle(callback, 100);
var enableHeadingRefresh = function () {
if (! headingRefresh && navigator.compass) {
navigator.compass.watchHeading(throttled);
headingRefresh = true;
}
};
document.addEventListener("deviceready", enableHeadingRefresh);
DeviceOrientation = {
heading: function () {
headingDep.depend();
return heading;
}
};

View File

@@ -0,0 +1,16 @@
Package.describe({
summary: "Provides reactive device orientation on desktop and mobile.",
version: "1.0.0-cordova4"
});
Cordova.depends({
"org.apache.cordova.device-orientation": "0.3.8"
});
Package.on_use(function (api) {
api.use(["deps", "underscore"]);
api.add_files(["device-orientation.js"], "client");
api.export("DeviceOrientation", "client");
});

View File

@@ -0,0 +1,19 @@
{
"dependencies": [
[
"deps",
"1.0.0"
],
[
"meteor",
"1.0.0"
],
[
"underscore",
"1.0.0"
]
],
"pluginDependencies": [],
"toolVersion": "meteor-tool@1.0.0",
"format": "1.0"
}

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Extended and Extensible JSON library", summary: "Extended and Extensible JSON library",
version: '1.0.0' version: '1.0.1-rc0'
}); });
Package.on_use(function (api) { Package.on_use(function (api) {

View File

@@ -28,13 +28,19 @@ Facebook.requestCredential = function (options, credentialRequestCompleteCallbac
if (options && options.requestPermissions) if (options && options.requestPermissions)
scope = options.requestPermissions.join(','); scope = options.requestPermissions.join(',');
var loginStyle = OAuth._loginStyle('facebook', config, options);
var loginUrl = var loginUrl =
'https://www.facebook.com/dialog/oauth?client_id=' + config.appId + 'https://www.facebook.com/dialog/oauth?client_id=' + config.appId +
'&redirect_uri=' + Meteor.absoluteUrl('_oauth/facebook?close') + '&redirect_uri=' + OAuth._redirectUri('facebook', config) +
'&display=' + display + '&scope=' + scope + '&state=' + credentialToken; '&display=' + display + '&scope=' + scope +
'&state=' + OAuth._stateParam(loginStyle, credentialToken);
OAuth.showPopup( OAuth.launchLogin({
loginUrl, loginService: "facebook",
_.bind(credentialRequestCompleteCallback, null, credentialToken) loginStyle: loginStyle,
); loginUrl: loginUrl,
credentialRequestCompleteCallback: credentialRequestCompleteCallback,
credentialToken: credentialToken
});
}; };

View File

@@ -53,7 +53,7 @@ var getTokenResponse = function (query) {
"https://graph.facebook.com/oauth/access_token", { "https://graph.facebook.com/oauth/access_token", {
params: { params: {
client_id: config.appId, client_id: config.appId,
redirect_uri: Meteor.absoluteUrl("_oauth/facebook?close"), redirect_uri: OAuth._redirectUri('facebook', config),
client_secret: OAuth.openSecret(config.secret), client_secret: OAuth.openSecret(config.secret),
code: query.code code: query.code
} }

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Facebook OAuth flow", summary: "Facebook OAuth flow",
version: "1.0.0" version: "1.0.1-rc0"
}); });
Package.on_use(function(api) { Package.on_use(function(api) {

View File

@@ -1,18 +1,18 @@
Package.describe({ Package.describe({
summary: "Publish internal app statistics", summary: "Publish internal app statistics",
version: '1.0.0' version: '1.0.1-rc0'
}); });
Package.on_use(function (api) { Package.on_use(function (api) {
api.use(['underscore'], ['client', 'server']); api.use(['underscore'], ['client', 'server']);
api.use(['templating', 'mongo-livedata', 'livedata'], ['client']); api.use(['templating', 'mongo', 'ddp'], ['client']);
// Detect whether autopublish is used. // Detect whether autopublish is used.
api.use('autopublish', 'server', {weak: true}); api.use('autopublish', 'server', {weak: true});
// Unordered dependency on livedata, since livedata has a (weak) dependency on // Unordered dependency on livedata, since livedata has a (weak) dependency on
// us. // us.
api.use('livedata', 'server', {unordered: true}); api.use('ddp', 'server', {unordered: true});
api.add_files('facts.html', ['client']); api.add_files('facts.html', ['client']);
api.add_files('facts.js', ['client', 'server']); api.add_files('facts.js', ['client', 'server']);

1
packages/fastclick/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.build*

View File

@@ -0,0 +1,821 @@
/**
* @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
*
* @version 1.0.3
* @codingstandard ftlabs-jsv2
* @copyright The Financial Times Limited [All Rights Reserved]
* @license MIT License (see LICENSE.txt)
*/
/*jslint browser:true, node:true*/
/*global define, Event, Node*/
/**
* Instantiate fast-clicking listeners on the specified layer.
*
* @constructor
* @param {Element} layer The layer to listen on
* @param {Object} options The options to override the defaults
*/
function FastClick(layer, options) {
'use strict';
var oldOnClick;
options = options || {};
/**
* Whether a click is currently being tracked.
*
* @type boolean
*/
this.trackingClick = false;
/**
* Timestamp for when click tracking started.
*
* @type number
*/
this.trackingClickStart = 0;
/**
* The element being tracked for a click.
*
* @type EventTarget
*/
this.targetElement = null;
/**
* X-coordinate of touch start event.
*
* @type number
*/
this.touchStartX = 0;
/**
* Y-coordinate of touch start event.
*
* @type number
*/
this.touchStartY = 0;
/**
* ID of the last touch, retrieved from Touch.identifier.
*
* @type number
*/
this.lastTouchIdentifier = 0;
/**
* Touchmove boundary, beyond which a click will be cancelled.
*
* @type number
*/
this.touchBoundary = options.touchBoundary || 10;
/**
* The FastClick layer.
*
* @type Element
*/
this.layer = layer;
/**
* The minimum time between tap(touchstart and touchend) events
*
* @type number
*/
this.tapDelay = options.tapDelay || 200;
if (FastClick.notNeeded(layer)) {
return;
}
// Some old versions of Android don't have Function.prototype.bind
function bind(method, context) {
return function() { return method.apply(context, arguments); };
}
var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel'];
var context = this;
for (var i = 0, l = methods.length; i < l; i++) {
context[methods[i]] = bind(context[methods[i]], context);
}
// Set up event handlers as required
if (deviceIsAndroid) {
layer.addEventListener('mouseover', this.onMouse, true);
layer.addEventListener('mousedown', this.onMouse, true);
layer.addEventListener('mouseup', this.onMouse, true);
}
layer.addEventListener('click', this.onClick, true);
layer.addEventListener('touchstart', this.onTouchStart, false);
layer.addEventListener('touchmove', this.onTouchMove, false);
layer.addEventListener('touchend', this.onTouchEnd, false);
layer.addEventListener('touchcancel', this.onTouchCancel, false);
// Hack is required for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
// which is how FastClick normally stops click events bubbling to callbacks registered on the FastClick
// layer when they are cancelled.
if (!Event.prototype.stopImmediatePropagation) {
layer.removeEventListener = function(type, callback, capture) {
var rmv = Node.prototype.removeEventListener;
if (type === 'click') {
rmv.call(layer, type, callback.hijacked || callback, capture);
} else {
rmv.call(layer, type, callback, capture);
}
};
layer.addEventListener = function(type, callback, capture) {
var adv = Node.prototype.addEventListener;
if (type === 'click') {
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
if (!event.propagationStopped) {
callback(event);
}
}), capture);
} else {
adv.call(layer, type, callback, capture);
}
};
}
// If a handler is already declared in the element's onclick attribute, it will be fired before
// FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
// adding it as listener.
if (typeof layer.onclick === 'function') {
// Android browser on at least 3.2 requires a new reference to the function in layer.onclick
// - the old one won't work if passed to addEventListener directly.
oldOnClick = layer.onclick;
layer.addEventListener('click', function(event) {
oldOnClick(event);
}, false);
layer.onclick = null;
}
}
/**
* Android requires exceptions.
*
* @type boolean
*/
var deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
/**
* iOS requires exceptions.
*
* @type boolean
*/
var deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent);
/**
* iOS 4 requires an exception for select elements.
*
* @type boolean
*/
var deviceIsIOS4 = deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent);
/**
* iOS 6.0(+?) requires the target element to be manually derived
*
* @type boolean
*/
var deviceIsIOSWithBadTarget = deviceIsIOS && (/OS ([6-9]|\d{2})_\d/).test(navigator.userAgent);
/**
* BlackBerry requires exceptions.
*
* @type boolean
*/
var deviceIsBlackBerry10 = navigator.userAgent.indexOf('BB10') > 0;
/**
* Determine whether a given element requires a native click.
*
* @param {EventTarget|Element} target Target DOM element
* @returns {boolean} Returns true if the element needs a native click
*/
FastClick.prototype.needsClick = function(target) {
'use strict';
switch (target.nodeName.toLowerCase()) {
// Don't send a synthetic click to disabled inputs (issue #62)
case 'button':
case 'select':
case 'textarea':
if (target.disabled) {
return true;
}
break;
case 'input':
// File inputs need real clicks on iOS 6 due to a browser bug (issue #68)
if ((deviceIsIOS && target.type === 'file') || target.disabled) {
return true;
}
break;
case 'label':
case 'video':
return true;
}
return (/\bneedsclick\b/).test(target.className);
};
/**
* Determine whether a given element requires a call to focus to simulate click into element.
*
* @param {EventTarget|Element} target Target DOM element
* @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
*/
FastClick.prototype.needsFocus = function(target) {
'use strict';
switch (target.nodeName.toLowerCase()) {
case 'textarea':
return true;
case 'select':
return !deviceIsAndroid;
case 'input':
switch (target.type) {
case 'button':
case 'checkbox':
case 'file':
case 'image':
case 'radio':
case 'submit':
return false;
}
// No point in attempting to focus disabled inputs
return !target.disabled && !target.readOnly;
default:
return (/\bneedsfocus\b/).test(target.className);
}
};
/**
* Send a click event to the specified element.
*
* @param {EventTarget|Element} targetElement
* @param {Event} event
*/
FastClick.prototype.sendClick = function(targetElement, event) {
'use strict';
var clickEvent, touch;
// On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
if (document.activeElement && document.activeElement !== targetElement) {
document.activeElement.blur();
}
touch = event.changedTouches[0];
// Synthesise a click event, with an extra attribute so it can be tracked
clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);
};
FastClick.prototype.determineEventType = function(targetElement) {
'use strict';
//Issue #159: Android Chrome Select Box does not open with a synthetic click event
if (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') {
return 'mousedown';
}
return 'click';
};
/**
* @param {EventTarget|Element} targetElement
*/
FastClick.prototype.focus = function(targetElement) {
'use strict';
var length;
// Issue #160: on iOS 7, some input elements (e.g. date datetime) throw a vague TypeError on setSelectionRange. These elements don't have an integer value for the selectionStart and selectionEnd properties, but unfortunately that can't be used for detection because accessing the properties also throws a TypeError. Just check the type instead. Filed as Apple bug #15122724.
if (deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time') {
length = targetElement.value.length;
targetElement.setSelectionRange(length, length);
} else {
targetElement.focus();
}
};
/**
* Check whether the given target element is a child of a scrollable layer and if so, set a flag on it.
*
* @param {EventTarget|Element} targetElement
*/
FastClick.prototype.updateScrollParent = function(targetElement) {
'use strict';
var scrollParent, parentElement;
scrollParent = targetElement.fastClickScrollParent;
// Attempt to discover whether the target element is contained within a scrollable layer. Re-check if the
// target element was moved to another parent.
if (!scrollParent || !scrollParent.contains(targetElement)) {
parentElement = targetElement;
do {
if (parentElement.scrollHeight > parentElement.offsetHeight) {
scrollParent = parentElement;
targetElement.fastClickScrollParent = parentElement;
break;
}
parentElement = parentElement.parentElement;
} while (parentElement);
}
// Always update the scroll top tracker if possible.
if (scrollParent) {
scrollParent.fastClickLastScrollTop = scrollParent.scrollTop;
}
};
/**
* @param {EventTarget} targetElement
* @returns {Element|EventTarget}
*/
FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) {
'use strict';
// On some older browsers (notably Safari on iOS 4.1 - see issue #56) the event target may be a text node.
if (eventTarget.nodeType === Node.TEXT_NODE) {
return eventTarget.parentNode;
}
return eventTarget;
};
/**
* On touch start, record the position and scroll offset.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchStart = function(event) {
'use strict';
var targetElement, touch, selection;
// Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111).
if (event.targetTouches.length > 1) {
return true;
}
targetElement = this.getTargetElementFromEventTarget(event.target);
touch = event.targetTouches[0];
if (deviceIsIOS) {
// Only trusted events will deselect text on iOS (issue #49)
selection = window.getSelection();
if (selection.rangeCount && !selection.isCollapsed) {
return true;
}
if (!deviceIsIOS4) {
// Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23):
// when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
// with the same identifier as the touch event that previously triggered the click that triggered the alert.
// Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an
// immediately preceeding touch event (issue #52), so this fix is unavailable on that platform.
// Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string,
// which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long,
// random integers, it's safe to to continue if the identifier is 0 here.
if (touch.identifier && touch.identifier === this.lastTouchIdentifier) {
event.preventDefault();
return false;
}
this.lastTouchIdentifier = touch.identifier;
// If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
// 1) the user does a fling scroll on the scrollable layer
// 2) the user stops the fling scroll with another tap
// then the event.target of the last 'touchend' event will be the element that was under the user's finger
// when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
// is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
this.updateScrollParent(targetElement);
}
}
this.trackingClick = true;
this.trackingClickStart = event.timeStamp;
this.targetElement = targetElement;
this.touchStartX = touch.pageX;
this.touchStartY = touch.pageY;
// Prevent phantom clicks on fast double-tap (issue #36)
if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
event.preventDefault();
}
return true;
};
/**
* Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.touchHasMoved = function(event) {
'use strict';
var touch = event.changedTouches[0], boundary = this.touchBoundary;
if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) {
return true;
}
return false;
};
/**
* Update the last position.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchMove = function(event) {
'use strict';
if (!this.trackingClick) {
return true;
}
// If the touch has moved, cancel the click tracking
if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
this.trackingClick = false;
this.targetElement = null;
}
return true;
};
/**
* Attempt to find the labelled control for the given label element.
*
* @param {EventTarget|HTMLLabelElement} labelElement
* @returns {Element|null}
*/
FastClick.prototype.findControl = function(labelElement) {
'use strict';
// Fast path for newer browsers supporting the HTML5 control attribute
if (labelElement.control !== undefined) {
return labelElement.control;
}
// All browsers under test that support touch events also support the HTML5 htmlFor attribute
if (labelElement.htmlFor) {
return document.getElementById(labelElement.htmlFor);
}
// If no for attribute exists, attempt to retrieve the first labellable descendant element
// the list of which is defined here: http://www.w3.org/TR/html5/forms.html#category-label
return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
};
/**
* On touch end, determine whether to send a click event at once.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchEnd = function(event) {
'use strict';
var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
if (!this.trackingClick) {
return true;
}
// Prevent phantom clicks on fast double-tap (issue #36)
if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
this.cancelNextClick = true;
return true;
}
// Reset to prevent wrong click cancel on input (issue #156).
this.cancelNextClick = false;
this.lastClickTime = event.timeStamp;
trackingClickStart = this.trackingClickStart;
this.trackingClick = false;
this.trackingClickStart = 0;
// On some iOS devices, the targetElement supplied with the event is invalid if the layer
// is performing a transition or scroll, and has to be re-detected manually. Note that
// for this to function correctly, it must be called *after* the event target is checked!
// See issue #57; also filed as rdar://13048589 .
if (deviceIsIOSWithBadTarget) {
touch = event.changedTouches[0];
// In certain cases arguments of elementFromPoint can be negative, so prevent setting targetElement to null
targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
}
targetTagName = targetElement.tagName.toLowerCase();
if (targetTagName === 'label') {
forElement = this.findControl(targetElement);
if (forElement) {
this.focus(targetElement);
if (deviceIsAndroid) {
return false;
}
targetElement = forElement;
}
} else if (this.needsFocus(targetElement)) {
// Case 1: If the touch started a while ago (best guess is 100ms based on tests for issue #36) then focus will be triggered anyway. Return early and unset the target element reference so that the subsequent click will be allowed through.
// Case 2: Without this exception for input elements tapped when the document is contained in an iframe, then any inputted text won't be visible even though the value attribute is updated as the user types (issue #37).
if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
this.targetElement = null;
return false;
}
this.focus(targetElement);
this.sendClick(targetElement, event);
// Select elements need the event to go through on iOS 4, otherwise the selector menu won't open.
// Also this breaks opening selects when VoiceOver is active on iOS6, iOS7 (and possibly others)
if (!deviceIsIOS || targetTagName !== 'select') {
this.targetElement = null;
event.preventDefault();
}
return false;
}
if (deviceIsIOS && !deviceIsIOS4) {
// Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled
// and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42).
scrollParent = targetElement.fastClickScrollParent;
if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
return true;
}
}
// Prevent the actual click from going though - unless the target node is marked as requiring
// real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted.
if (!this.needsClick(targetElement)) {
event.preventDefault();
this.sendClick(targetElement, event);
}
return false;
};
/**
* On touch cancel, stop tracking the click.
*
* @returns {void}
*/
FastClick.prototype.onTouchCancel = function() {
'use strict';
this.trackingClick = false;
this.targetElement = null;
};
/**
* Determine mouse events which should be permitted.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onMouse = function(event) {
'use strict';
// If a target element was never set (because a touch event was never fired) allow the event
if (!this.targetElement) {
return true;
}
if (event.forwardedTouchEvent) {
return true;
}
// Programmatically generated events targeting a specific element should be permitted
if (!event.cancelable) {
return true;
}
// Derive and check the target element to see whether the mouse event needs to be permitted;
// unless explicitly enabled, prevent non-touch click events from triggering actions,
// to prevent ghost/doubleclicks.
if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
// Prevent any user-added listeners declared on FastClick element from being fired.
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
} else {
// Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
event.propagationStopped = true;
}
// Cancel the event
event.stopPropagation();
event.preventDefault();
return false;
}
// If the mouse event is permitted, return true for the action to go through.
return true;
};
/**
* On actual clicks, determine whether this is a touch-generated click, a click action occurring
* naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
* an actual click which should be permitted.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onClick = function(event) {
'use strict';
var permitted;
// It's possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early.
if (this.trackingClick) {
this.targetElement = null;
this.trackingClick = false;
return true;
}
// Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target.
if (event.target.type === 'submit' && event.detail === 0) {
return true;
}
permitted = this.onMouse(event);
// Only unset targetElement if the click is not permitted. This will ensure that the check for !targetElement in onMouse fails and the browser's click doesn't go through.
if (!permitted) {
this.targetElement = null;
}
// If clicks are permitted, return true for the action to go through.
return permitted;
};
/**
* Remove all FastClick's event listeners.
*
* @returns {void}
*/
FastClick.prototype.destroy = function() {
'use strict';
var layer = this.layer;
if (deviceIsAndroid) {
layer.removeEventListener('mouseover', this.onMouse, true);
layer.removeEventListener('mousedown', this.onMouse, true);
layer.removeEventListener('mouseup', this.onMouse, true);
}
layer.removeEventListener('click', this.onClick, true);
layer.removeEventListener('touchstart', this.onTouchStart, false);
layer.removeEventListener('touchmove', this.onTouchMove, false);
layer.removeEventListener('touchend', this.onTouchEnd, false);
layer.removeEventListener('touchcancel', this.onTouchCancel, false);
};
/**
* Check whether FastClick is needed.
*
* @param {Element} layer The layer to listen on
*/
FastClick.notNeeded = function(layer) {
'use strict';
var metaViewport;
var chromeVersion;
var blackberryVersion;
// Devices that don't support touch don't need FastClick
if (typeof window.ontouchstart === 'undefined') {
return true;
}
// Chrome version - zero for other browsers
chromeVersion = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];
if (chromeVersion) {
if (deviceIsAndroid) {
metaViewport = document.querySelector('meta[name=viewport]');
if (metaViewport) {
// Chrome on Android with user-scalable="no" doesn't need FastClick (issue #89)
if (metaViewport.content.indexOf('user-scalable=no') !== -1) {
return true;
}
// Chrome 32 and above with width=device-width or less don't need FastClick
if (chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) {
return true;
}
}
// Chrome desktop doesn't need FastClick (issue #15)
} else {
return true;
}
}
if (deviceIsBlackBerry10) {
blackberryVersion = navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/);
// BlackBerry 10.3+ does not require Fastclick library.
// https://github.com/ftlabs/fastclick/issues/251
if (blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) {
metaViewport = document.querySelector('meta[name=viewport]');
if (metaViewport) {
// user-scalable=no eliminates click delay.
if (metaViewport.content.indexOf('user-scalable=no') !== -1) {
return true;
}
// width=device-width (or less than device-width) eliminates click delay.
if (document.documentElement.scrollWidth <= window.outerWidth) {
return true;
}
}
}
}
// IE10 with -ms-touch-action: none, which disables double-tap-to-zoom (issue #97)
if (layer.style.msTouchAction === 'none') {
return true;
}
return false;
};
/**
* Factory method for creating a FastClick object
*
* @param {Element} layer The layer to listen on
* @param {Object} options The options to override the defaults
*/
FastClick.attach = function(layer, options) {
'use strict';
return new FastClick(layer, options);
};
if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
// AMD. Register as an anonymous module.
define(function() {
'use strict';
return FastClick;
});
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = FastClick.attach;
module.exports.FastClick = FastClick;
} else {
window.FastClick = FastClick;
}

View File

@@ -0,0 +1,10 @@
Package.describe({
summary: "Faster touch events on mobile",
version: '1.0.0-cordova1'
});
Package.on_use(function (api) {
api.export('FastClick', 'web.cordova');
api.addFiles(['pre.js', 'fastclick.js', 'post.js'], 'web.cordova');
});

View File

@@ -0,0 +1,8 @@
// This exports object was created in pre.js. Now copy the 'FastClick' object
// from it into the package-scope variable `FastClick`, which will get exported.
FastClick = module.exports.FastClick;
Meteor.startup(function () {
FastClick.attach(document.body);
});

View File

@@ -0,0 +1,6 @@
// Define an object named module.exports. This will cause fastclick.js to put
// FastClick as a field on it, instead of in the global namespace.
// See also post.js.
module = {
exports: {}
};

View File

@@ -4,7 +4,7 @@ Package.describe({
}); });
Package.on_use(function (api) { Package.on_use(function (api) {
api.use(['logging', 'underscore', 'livedata', 'ejson']); api.use(['logging', 'underscore', 'ddp', 'ejson']);
api.add_files(['follower.js'], 'server'); api.add_files(['follower.js'], 'server');
api.export('Follower'); api.export('Follower');
}); });

View File

@@ -8,7 +8,7 @@ Package.on_use(function (api) {
api.use('underscore'); api.use('underscore');
// make sure we come after livedata, so we load after the sockjs // make sure we come after livedata, so we load after the sockjs
// server has been instantiated. // server has been instantiated.
api.use('livedata', 'server'); api.use('ddp', 'server');
api.add_files('force_ssl_common.js', ['client', 'server']); api.add_files('force_ssl_common.js', ['client', 'server']);
api.add_files('force_ssl_server.js', 'server'); api.add_files('force_ssl_server.js', 'server');

1
packages/geolocation/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.build*

View File

@@ -0,0 +1,45 @@
var locationDep = new Deps.Dependency();
var location = null;
var locationRefresh = false;
var options = {
enableHighAccuracy: true,
maximumAge: 0
};
var errCallback = function () {
// do nothing
};
var callback = function (newLocation) {
location = newLocation;
locationDep.changed();
};
var enableLocationRefresh = function () {
if (! locationRefresh && navigator.geolocation) {
navigator.geolocation.watchPosition(callback, errCallback, options);
locationRefresh = true;
}
};
Geolocation = {
currentLocation: function () {
enableLocationRefresh();
locationDep.depend();
return location;
},
// simple version of location; just lat and lng
latLng: function () {
var loc = Geolocation.currentLocation();
if (loc) {
return {
lat: loc.coords.latitude,
lng: loc.coords.longitude
};
}
return null;
}
};

View File

@@ -0,0 +1,16 @@
Package.describe({
summary: "Provides reactive geolocation on desktop and mobile.",
version: "1.0.0-cordova2"
});
Cordova.depends({
"org.apache.cordova.geolocation": "0.3.9"
});
Package.on_use(function (api) {
api.use(["deps"]);
api.add_files(["geolocation.js"], "client");
api.export("Geolocation", "client");
});

View File

@@ -0,0 +1,19 @@
{
"dependencies": [
[
"deps",
"1.0.0"
],
[
"meteor",
"1.0.0"
],
[
"underscore",
"1.0.0"
]
],
"pluginDependencies": [],
"toolVersion": "meteor-tool@1.0.0",
"format": "1.0"
}

View File

@@ -23,16 +23,21 @@ Github.requestCredential = function (options, credentialRequestCompleteCallback)
var scope = (options && options.requestPermissions) || []; var scope = (options && options.requestPermissions) || [];
var flatScope = _.map(scope, encodeURIComponent).join('+'); var flatScope = _.map(scope, encodeURIComponent).join('+');
var loginUrl = var loginStyle = OAuth._loginStyle('github', config, options);
'https://github.com/login/oauth/authorize' +
'?client_id=' + config.clientId +
'&scope=' + flatScope +
'&redirect_uri=' + Meteor.absoluteUrl('_oauth/github?close') +
'&state=' + credentialToken;
OAuth.showPopup( var loginUrl =
loginUrl, 'https://github.com/login/oauth/authorize' +
_.bind(credentialRequestCompleteCallback, null, credentialToken), '?client_id=' + config.clientId +
{width: 900, height: 450} '&scope=' + flatScope +
); '&redirect_uri=' + OAuth._redirectUri('github', config) +
'&state=' + OAuth._stateParam(loginStyle, credentialToken);
OAuth.launchLogin({
loginService: "github",
loginStyle: loginStyle,
loginUrl: loginUrl,
credentialRequestCompleteCallback: credentialRequestCompleteCallback,
credentialToken: credentialToken,
popupOptons: {width: 900, height: 450}
});
}; };

View File

@@ -10,7 +10,7 @@
Set Homepage URL to: <span class="url">{{siteUrl}}</span> Set Homepage URL to: <span class="url">{{siteUrl}}</span>
</li> </li>
<li> <li>
Set Authorization callback URL to: <span class="url">{{siteUrl}}_oauth/github?close</span> Set Authorization callback URL to: <span class="url">{{siteUrl}}_oauth/github</span>
</li> </li>
</ol> </ol>
</template> </template>

View File

@@ -38,7 +38,7 @@ var getAccessToken = function (query) {
code: query.code, code: query.code,
client_id: config.clientId, client_id: config.clientId,
client_secret: OAuth.openSecret(config.secret), client_secret: OAuth.openSecret(config.secret),
redirect_uri: Meteor.absoluteUrl("_oauth/github?close"), redirect_uri: OAuth._redirectUri('github', config),
state: query.state state: query.state
} }
}); });

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Github OAuth flow", summary: "Github OAuth flow",
version: "1.0.0" version: "1.0.1-rc0"
}); });
Package.on_use(function(api) { Package.on_use(function(api) {

View File

@@ -35,13 +35,15 @@ Google.requestCredential = function (options, credentialRequestCompleteCallback)
var accessType = options.requestOfflineToken ? 'offline' : 'online'; var accessType = options.requestOfflineToken ? 'offline' : 'online';
var approvalPrompt = options.forceApprovalPrompt ? 'force' : 'auto'; var approvalPrompt = options.forceApprovalPrompt ? 'force' : 'auto';
var loginStyle = OAuth._loginStyle('google', config, options);
var loginUrl = var loginUrl =
'https://accounts.google.com/o/oauth2/auth' + 'https://accounts.google.com/o/oauth2/auth' +
'?response_type=code' + '?response_type=code' +
'&client_id=' + config.clientId + '&client_id=' + config.clientId +
'&scope=' + flatScope + '&scope=' + flatScope +
'&redirect_uri=' + Meteor.absoluteUrl('_oauth/google?close') + '&redirect_uri=' + OAuth._redirectUri('google', config) +
'&state=' + credentialToken + '&state=' + OAuth._stateParam(loginStyle, credentialToken) +
'&access_type=' + accessType + '&access_type=' + accessType +
'&approval_prompt=' + approvalPrompt; '&approval_prompt=' + approvalPrompt;
@@ -54,9 +56,12 @@ Google.requestCredential = function (options, credentialRequestCompleteCallback)
loginUrl += '&hd=' + encodeURIComponent(Accounts._options.restrictCreationByEmailDomain); loginUrl += '&hd=' + encodeURIComponent(Accounts._options.restrictCreationByEmailDomain);
} }
OAuth.showPopup( OAuth.launchLogin({
loginUrl, loginService: "google",
_.bind(credentialRequestCompleteCallback, null, credentialToken), loginStyle: loginStyle,
{ height: 406 } loginUrl: loginUrl,
); credentialRequestCompleteCallback: credentialRequestCompleteCallback,
credentialToken: credentialToken,
popupOptions: { height: 406 }
});
}; };

View File

@@ -22,7 +22,7 @@
Set Authorized Javascript Origins to: <span class="url">{{siteUrl}}</span> Set Authorized Javascript Origins to: <span class="url">{{siteUrl}}</span>
</li> </li>
<li> <li>
Set Authorized Redirect URI to: <span class="url">{{siteUrl}}_oauth/google?close</span> Set Authorized Redirect URI to: <span class="url">{{siteUrl}}_oauth/google</span>
</li> </li>
<li> <li>
Click "Create Client ID" Click "Create Client ID"

View File

@@ -47,7 +47,7 @@ var getTokens = function (query) {
code: query.code, code: query.code,
client_id: config.clientId, client_id: config.clientId,
client_secret: OAuth.openSecret(config.secret), client_secret: OAuth.openSecret(config.secret),
redirect_uri: Meteor.absoluteUrl("_oauth/google?close"), redirect_uri: OAuth._redirectUri('google', config),
grant_type: 'authorization_code' grant_type: 'authorization_code'
}}); }});
} catch (err) { } catch (err) {

View File

@@ -1,6 +1,6 @@
Package.describe({ Package.describe({
summary: "Google OAuth flow", summary: "Google OAuth flow",
version: "1.0.0" version: "1.0.1-rc0"
}); });
Package.on_use(function(api) { Package.on_use(function(api) {

View File

@@ -1,9 +1,10 @@
Package.describe({ Package.describe({
summary: "Small library for expressing HTML trees", summary: "Small library for expressing HTML trees",
version: '1.0.0' version: '1.0.0-cordova1'
}); });
Package.on_use(function (api) { Package.on_use(function (api) {
api.use('deps');
api.export('HTML'); api.export('HTML');
api.add_files(['preamble.js', api.add_files(['preamble.js',

View File

@@ -46,9 +46,7 @@ HTTP.call = function(method, url, options, callback) {
else else
params_for_body = options.params; params_for_body = options.params;
var query_match = /^(.*?)(\?.*)?$/.exec(url); url = URL._constructUrl(url, options.query, params_for_url);
url = buildUrl(query_match[1], query_match[2],
options.query, params_for_url);
if (options.followRedirects === false) if (options.followRedirects === false)
throw new Error("Option followRedirects:false not supported on client."); throw new Error("Option followRedirects:false not supported on client.");
@@ -63,7 +61,7 @@ HTTP.call = function(method, url, options, callback) {
} }
if (params_for_body) { if (params_for_body) {
content = encodeParams(params_for_body); content = URL._encodeParams(params_for_body);
} }
_.extend(headers, options.headers || {}); _.extend(headers, options.headers || {});

View File

@@ -12,41 +12,6 @@ makeErrorByStatus = function(statusCode, content) {
return new Error(message); return new Error(message);
}; };
encodeParams = function(params) {
var buf = [];
_.each(params, function(value, key) {
if (buf.length)
buf.push('&');
buf.push(encodeString(key), '=', encodeString(value));
});
return buf.join('').replace(/%20/g, '+');
};
encodeString = function(str) {
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
};
buildUrl = function(before_qmark, from_qmark, opt_query, opt_params) {
var url_without_query = before_qmark;
var query = from_qmark ? from_qmark.slice(1) : null;
if (typeof opt_query === "string")
query = String(opt_query);
if (opt_params) {
query = query || "";
var prms = encodeParams(opt_params);
if (query && prms)
query += '&';
query += prms;
}
var url = url_without_query;
if (query !== null)
url += ("?"+query);
return url;
};
// Fill in `response.data` if the content-type is JSON. // Fill in `response.data` if the content-type is JSON.
populateData = function(response) { populateData = function(response) {

View File

@@ -21,8 +21,6 @@ var _call = function(method, url, options, callback) {
if (! /^https?:\/\//.test(url)) if (! /^https?:\/\//.test(url))
throw new Error("url must be absolute and start with http:// or https://"); throw new Error("url must be absolute and start with http:// or https://");
var url_parts = url_util.parse(url);
var headers = {}; var headers = {};
var content = options.content; var content = options.content;
@@ -38,9 +36,7 @@ var _call = function(method, url, options, callback) {
else else
params_for_body = options.params; params_for_body = options.params;
var new_url = buildUrl( var new_url = URL._constructUrl(url, options.query, params_for_url);
url_parts.protocol + "//" + url_parts.host + url_parts.pathname,
url_parts.search, options.query, params_for_url);
if (options.auth) { if (options.auth) {
if (options.auth.indexOf(':') < 0) if (options.auth.indexOf(':') < 0)
@@ -50,7 +46,7 @@ var _call = function(method, url, options, callback) {
} }
if (params_for_body) { if (params_for_body) {
content = encodeParams(params_for_body); content = URL._encodeParams(params_for_body);
headers['Content-Type'] = "application/x-www-form-urlencoded"; headers['Content-Type'] = "application/x-www-form-urlencoded";
} }

View File

@@ -451,7 +451,7 @@ if (Meteor.isServer) {
}; };
// existing static file // existing static file
do_test("/packages/local-test:http/test_static.serveme", 200, /static file serving/); do_test("/packages/local-test_http/test_static.serveme", 200, /static file serving/);
// no such file, so return the default app HTML. // no such file, so return the default app HTML.
var getsAppHtml = [ var getsAppHtml = [

Some files were not shown because too many files have changed in this diff Show More