Using DDP in a Chrome extension - meteor

In the Chrome extension I am building, I would like to connect to a Meteor server from the extensions popup window, for user registration, login and
I've found a brief explanation from Meteor Spotting from 2014 that indicates that it is possible to do this using ddp.js, but the project it describes appears to have disappeared.
The current version of ddp.js uses import statements, which Chrome objects to. My understanding is that these scripts are designed for Node.js. How should I edit these scripts so that they can be used in a Chrome extension as described in the Meteor Spotting article?
Alternatively, is there any example of a bare-bones Chrome extension which connects with a bare-bones Meteor server via DPP, that I can use for inspiration?

I have found my own solution. Here is a bare-bones implementation of a call to a Meteor server from a Chrome extension:
manifest.json
{
"manifest_version": 2
, "name": "DDP Test"
, "version": "0.1"
, "background": {
"scripts": [
"jquery-3.1.0.js"
, "meteor-ddp.js"
, "meteor.js"
, "background.js"
]
}
}
jquery-3.1.0.js can be found here
eddfirs meteor-ddp.js can be found here
meteor.js (adapted from Meteor Spotting)
var Meteor
;(function meteor(){
"use strict"
var endpoint = "ws://localhost:3000/websocket"
// Use your own endpoint ^^^
Meteor = {
call : function(){
var args = [].slice.call(arguments)
var methodName = args.shift()
var callback = args.pop()
var ddp = new MeteorDdp(endpoint)
if (!(callback instanceof Function)) {
args.push(callback)
callback = function (error, data) {
console.log("Result of "+methodName+" call:", error, data)
}
}
ddp.connect().done(MeteorCall)
function MeteorCall() {
console.log('Connected!');
var $deferred = ddp.call(methodName, args)
$deferred.done(function (result) {
callback(null, result)
})
$deferred.fail(function (error) {
callback(error)
})
}
}
};
})()
background.js
;(function background(){
"use strict"
Meteor.call("test", 1, "two", { three: [4]})
Meteor.call("test", "using", "a", "custom", callback)
function callback(error, result) {
console.log("Custom callback error:", error, ", Result:", result)
}
})()
Output in the server console when the extension is loaded:
I20160917-19:35:19.352(-4)? test 1 two { three: [ 4 ] }
I20160917-19:35:19.377(-4)? test using a custom
Output in the Inspector for the background view:
Connected!
meteor.js:18 Result of test call: null test method activated with arguments {"0":1,"1":"two","2":{"three":[4]}}
meteor.js:25 Connected!
background.js:8 Custom callback error: null , Result: test method activated with arguments {"0":"using","1":"a","2":"custom"}

Related

Meteor: callLoginMethod not found error

I'm having difficulty invoking a login method, it follows
$ meteor list
Accounts-base 1.2.14 A user account system
Ecmascript 0.6.1 Compiler plugin that supports ES2015 + in all .js files
Meteor-base 1.0.4 Packages that every Meteor app needs
React 15.0.1 Everything you need to use React with Meteor.
Static-html 1.1.13 Defines static page content in .html files
/server/main.js
import { Accounts } from 'meteor/accounts-base'
Accounts.registerLoginHandler('simples', (ttt) => {
console.log(ttt);
});
/client/main.js
autenticar(){
Accounts.callLoginMethod({
methodName: 'simples',
methodArguments: [{ tipo : 'simples' }],
validateResult: function (result) {
console.log('result', result);
},
userCallback: function(error) {
if (error) {
console.log('error', error);
}
}
})
}
When calling authenticar(), I get this error:
errorClass
  Details: undefined
  Error: 404
  ErrorType: "Meteor.Error"
  Message: "Method 'simples' not found [404]"
  Reason: "Method 'simples' not found"
Where is the error?
I've never used this API personally, but from a quick glance through the Meteor internals, I see a couple issues.
Accounts.registerLoginHandler only adds an additional handler to an array of built-in handlers which are called as part of the default Meteor login process.
If you are trying to plug in an additional handler into the existing process, you should call Accounts.callLoginMethod without the methodName key.
Calling Accounts.callLoginMethod with methodName will bypass the built-in handlers completely and replace them with your custom method, however this method needs to be declared separately by you with Meteor.methods, not registerLoginHandler.
So, that's probably your error -- you need to define your simples method with Meteor.methods. Also, you should check the code for the requirements of this method, see the comments in the code here:
https://github.com/meteor/meteor/blob/devel/packages/accounts-base/accounts_client.js
Only to complement and keep as a referral for someone else to get here. That way it's working
client.js
Accounts.callLoginMethod({
methodArguments: [{tipo: 'simples'}],
validateResult: (result) => {
console.log('success', result);
},
userCallback: function(error) {
if (error) {
console.log('error', error);
}
}
});
server.js
Meteor.startup(function () {
var config = Accounts.loginServiceConfiguration.findOne({
service : 'simples'
});
if (!config) {
Accounts.loginServiceConfiguration.insert({ service: 'simples' });
}
});
Accounts.registerLoginHandler((opts) => {
if(opts.tipo === 'simples'){
return Accounts.updateOrCreateUserFromExternalService ('simples', {
id: 0 // need define something
}, {
options : 'optional'
})
}
});

Working with Meteor and Async balanced-payments functions

I'm using balanced-payments and their version 1.1 of balanced.js within Meteor.
I'm trying to create a new customer using
balanced.marketplace.customers.create(formData);
Here is my CheckFormSubmitEvents.js file
Template.CheckFormSubmit.events({
'submit form': function (e, tmpl) {
e.preventDefault();
var recurringStatus = $(e.target).find('[name=is_recurring]').is(':checked');
var checkForm = {
name: $(e.target).find('[name=name]').val(),
account_number: $(e.target).find('[name=account_number]').val(),
routing_number: $(e.target).find('[name=routing_number]').val(),
recurring: { is_recurring: recurringStatus },
created_at: new Date
}
checkForm._id = Donations.insert(checkForm);
Meteor.call("balancedCardCreate", checkForm, function(error, result) {
console.log(result);
// Successful tokenization
if(result.status_code === 201 && result.href) {
// Send to your backend
jQuery.post(responseTarget, {
uri: result.href
}, function(r) {
// Check your backend result
if(r.status === 201) {
// Your successful logic here from backend
} else {
// Your failure logic here from backend
}
});
} else {
// Failed to tokenize, your error logic here
}
// Debuging, just displays the tokenization result in a pretty div
$('#response .panel-body pre').html(JSON.stringify(result, false, 4));
$('#response').slideDown(300);
});
}
});
Here is my Methods.js file
var wrappedDelayedFunction = Async.wrap(balanced.marketplace.customers.create);
Meteor.methods({
balancedCardCreate: function (formData) {
console.log(formData);
var response = wrappedDelayedFunction(formData);
console.log(response);
return response;
}
});
I get nothing back when I submit the form, except that on the server console I do see the log of the form data.
I'm sure I'm not calling some of these async functions correctly. The hard part for me here is that the balanced function are async, but I don't know if they fit into the same mold as some of the examples I've seen.
I've tried to follow this example code.
http://meteorhacks.com/improved-async-utilities-in-meteor-npm.html
Is there a specific change that needs to be done in regard to working with balanced here? Does anyone have any tips for working with Async functions or see something specific about my code that I've done wrong?
Thanks
The NPM utilities Async.wrap does the same thing as the undocumented Meteor function Meteor._wrapAsync, in that it takes an asynchronous function with the last argument function(err, result) {} and turns it into a synchronous function which takes the same arguments, but either returns a result or throws an error instead of using the callback. The function yields in a Fiber until the asynchronous callback returns, so that other code in the event loop can run.
One pitfall with this is that you need to make sure that the function you wrap is called with the correct context. So if balanced.marketplace.customers.create is a prototype method that expects this to be set to something, it will not be set properly unless you bind it yourself, using function.bind or any of the other various library polyfills.
For more information, see https://stackoverflow.com/a/21542356/586086.
What I ended up doing was using a future. This works great, I just need to do better at catching errors. Which will be a question for a pro I think ; - )
Credit should go to user3374348 for answering another similar question of mine, which solved both of these.
https://stackoverflow.com/a/23777507/582309
var Future = Npm.require("fibers/future");
function extractFromPromise(promise) {
var fut = new Future();
promise.then(function (result) {
fut["return"](result);
}, function (error) {
fut["throw"](error);
});
return fut.wait();
}
Meteor.methods({
createCustomer: function (data) {
balanced.configure(Meteor.settings.balancedPaymentsAPI);
var customerData = extractFromPromise(balanced.marketplace.customers.create({
'name': data.fname + " " + data.lname,
"address": {
"city": data.city,
"state": data.region,
"line1": data.address_line1,
"line2": data.address_line2,
"postal_code": data.postal_code,
},
'email': data.email_address,
'phone': data.phone_number
}));
var card = extractFromPromise(balanced.marketplace.cards.create({
'number': data.card_number,
'expiration_year': data.expiry_year,
'expiration_month': data.expiry_month,
'cvv': data.cvv
}));
var associate = extractFromPromise(card.associate_to_customer(customerData.href).debit({
"amount": data.total_amount*100,
"appears_on_statement_as": "Trash Mountain" }));
});
As Andrew mentioned, you need to set the context for the method.
Here's the way you can do that with Async.wrap
Async.wrap(balanced.marketplace.customers, "create");

Angular service containing http get call not working with ng-repeat

I have an angular controller that calls a service. The service is responsible for returning data from a json file.
controller:
function projectController($scope, ajaxServices) {
$scope.projects = ajaxServices.getProjects();
}
service:
projectManagerApp.factory('ajaxServices', function ($http) {
return {
getProjects : function () {
$http.get('projects.json', { data: {} }).success(function (data) {
if (window.console && console.log) {
console.log("objects returned: " + data.length); // shows # of items
}
return data //nothing ng-repeated, no console errors.
})
// Exact same data from json file hard-coded, works fine
// when not commented out.
// return [{ "id": 1, "name": "Project 1 }, { "id": 2, "name": "Project 2" }]
}
}
});
html: ng-repeat="project in projects"
In the success function I can see the data returned in the console log but if I try to return the data the ng-repeat ul element on my page is empty. In the same service if I simply return the same data logged to the console hard coded (outside of the success function, of course it works just fine.
How can I return the data into the ng-repeat using my ajax call?
I'm just as new to Plunker as I am Angular but here is my attempt at a Plunk:
http://plnkr.co/edit/ALa9q6
$http is asynchronous, therefore the call to getProjects will return nothing. Using $q you can receive an instance to a promise which will receive the data when available.
Using $q
Here an example using $q:
http://plnkr.co/edit/U72oJblvrVEgYt2mTmU2?p=preview
Using $resource
Alternatively, you can use $resource especially if your server code is RESTful, which requires adding the following dependency in your scripts:
//ajax.googleapis.com/ajax/libs/angularjs/1.1.4/angular-resource.js
This is a 1st review of your code to use $resource: http://plnkr.co/edit/tLOAaXZHdGgWOok3Sdc8?p=preview
But you can simplify and shrink it more to this:
http://plnkr.co/edit/pKO6k6GxJ1RlO8SNvqUo?p=preview
This is the new app.js file:
angular.module('app', ['ngResource'])
.factory('ProjectsService', ['$resource', function($resource) {
return $resource('projects.json');
}])
.controller('ProjectsController', ['ProjectsService', '$scope', function(ProjectsService, $scope) {
$scope.projects = ProjectsService.query();
}]);
Find more information about $resource here:
http://docs.angularjs.org/api/ngResource.$resource
You need to use $q. Example is here
$http performs asynchronously and may or may not be finished at any given point in time which is why your return statement dont work.
Use $q or simply handle the promise inside the controller:
Service:
projectManagerApp.factory('ajaxServices', function ($http) {
return {
getProjects : function () {
return $http.get('projects.json', { data: {} })
}
}
});
Controller:
function projectController($scope, ajaxServices) {
ajaxServices.getProjects().success(function (data) {
if (window.console && console.log) {
console.log("objects returned: " + data.length);
}
$scope.projects = data
});
}
Plunk
If you want data to be loaded before the page
Is loaded you can use 'resolve' property
For the module.
Have a look in the docs for details.

"Uncaught TypeError: Cannot read property 'queue' of undefined" occured during backbone and signalR integration

I have a signalR server queue hab. I am trying to start the server and send a messege to the server when a button is clicked in my SearchResultListItemView view. But can not working and I am getting the error "Uncaught TypeError: Cannot read property 'queue' ".
This is my SearchResultListItemView view where i have to call the signalR server when the a click event is occured. I just want to sent some value to the server on click. Then I will sent response to all other client to load changes. How can i do this? or what's the problem here??
window.SearchResultListItemView = Backbone.View.extend({
tagName: "tr",
initialize: function () {
var _this = this;
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
// here is the error occured in this line:
var queue = $.connection.queue;
// Start the connection
$.connection.hub.start(function () {
queue.ReloadQueueMember("Hello World!", "hi all");
});
},
events: {
"click a": "JoinQueue"
},
JoinQueue: function (e) {
e.preventDefault();
var name = this.model.get("QueueName");
var Id = this.model.get("CustomerId");
//SignalR Proxy created on the fly
queue.send(name, 'hannan19')
.done(function () {
console.log('Success!')
})
.fail(function (e) {
console.warn(e);
});
},
render: function () {
var data = this.model.toJSON();
_.extend(data, this.attributes);
$(this.el).html(this.template(data));
return this;
}
});
And this is my SignalR server:
public class Queue : Hub
{
public void Send(string QueueName, string UserName)
{
Clients.ReloadQueueMember(QueueName, UserName);
}
}
Several things to check:
Make sure you're using the latest version off of NuGet (at the moment, 1.0.0-rc1; you'll need to check "include prerelease" to see it).
If $.connection is undefined, make sure you're loading the static SignalR client (at the moment, "jquery.signalR-1.0.0-rc1.js"). Check the "Network" tab of the developer tools of your favorite browser to confirm that it's being found and loaded.
If $.connection.queue is undefined, make sure you're loading the dynamic SignalR client (~/signalr/hubs).
This line is wrong (if you're using 1.0.0-rc1):
queue.ReloadQueueMember("Hello World!", "hi all");
It should be:
queue.client.reloadQueueMember("Hello World!", "hi all");
Similarly, this line would be wrong in 1.0.0-rc1:
queue.send(name, 'hannan19')
It should be:
queue.server.send(name, 'hannan19')

Jquery Ajax, not working in Internet explorer

I'm trying to do some jQuery ajax and it works in Firfox and Chrome, but not in internet explorer 9.
The final code will have to go across sub-domains, and this doesn't work in ie with the default transport.
So I'm trying to create a custom transport to use in internet explorer
Method 1
$.ajaxTransport("+*", function (options, originalOptions, jqXHR) {
if (jQuery.browser.msie && window.XDomainRequest) {
var xdr;
return {
send: function (headers, completeCallback) {
// Use Microsoft XDR
xdr = new XDomainRequest();
xdr.open("get", options.url);
xdr.onload = function () {
if (this.contentType.match(/\/xml/)) {
var dom = new ActiveXObject("Microsoft.XMLDOM");
dom.async = false;
dom.loadXML(this.responseText);
completeCallback(200, "success", [dom]);
} else {
completeCallback(200, "success", [this.responseText]);
}
};
xdr.ontimeout = function () {
completeCallback(408, "error", ["The request timed out."]);
};
xdr.onerror = function () {
completeCallback(404, "error", ["The requested resource could not be found."]);
};
xdr.send();
},
abort: function () {
if (xdr) xdr.abort();
}
};
}
});
I've created a simple sample page to demonstrate the first technique here:
http://services.whygo.net/sendAjax.htm
Please note if you use the custom transport the normal transport will then fail unless you refresh
The idea comes from here:
http://forum.jquery.com/topic/cross-domain-ajax-and-ie#14737000002203097
This give no error message other than 'error' inside the 'error' method called on $ajax, when it fails. I do get a 405 Method not allowed on the 'Network' tab of if dev tools, but the server side stuff does execute.
Method 2
I have also tried another method as described here:
Cross-subdomain AJAX works in Chrome, not IE
if ('XDomainRequest' in window && window.XDomainRequest !== null) {
// override default jQuery transport
jQuery.ajaxSettings.xhr = function() {
try { return new XDomainRequest(); }
catch(e) { }
};
}
This can be found here:
http://www.whygo.net/sendAjax2.html
On this one I actually get 200 codes on the 'network' tab of ie dev tools, but doesn't call the 'error' or the 'success' pararm of $ajax.
If I put a timeout on this second one, then it returns to the 'error' function with a message of 'timeout'.
Here's the solution I went with after about a day of struggling with this inconsistency...
// new method as to not overwrite jQuery's defaults
var cors = (window.XDomainRequest) ? function(url, callback) {
var xdr = new XDomainRequest();
xdr.open('get', url);
xdr.onload = function() { callback(xdr.responseText); }
xdr.send();
} : $.get; // else, use jQuery's method
Use...
cors(url, function(msg) { alert(msg); }); // pretty well same as $.get
Copy and paste, this of course doesn't serve all purposes, but it's a start and it works.
On the http://services.whygo.net/sendAjax2.html page, I see that you've got the expected dataType of the AJAX response coming back from the server as JSON, but the response actually comes back as a plain text string ("Email successfully sent.").
Perhaps you could try commenting out dataType and let jQuery figure out what type of response comes back.

Resources