toothlessgear / node-gcm Goto Github PK
View Code? Open in Web Editor NEWA NodeJS wrapper library port to send data to Android devices via Google Cloud Messaging
Home Page: https://github.com/ToothlessGear/node-gcm
License: Other
A NodeJS wrapper library port to send data to Android devices via Google Cloud Messaging
Home Page: https://github.com/ToothlessGear/node-gcm
License: Other
sender.send(message, registrationIds, 4, function(err, result) {
}
serder.js line 56
console.log(post_options);
the logs
{ method: 'POST',
headers:
{ 'Content-Type': 'application/json',
'Content-length': 324,
Authorization: 'key=AIzaSyDf-WNCqK-H7mmL4AiG9xxxxxxxxxxxxxxx' },
uri: 'https://android.googleapis.com:443/gcm/send',
body: '{"registration_ids":["APA91bFc7Te2qwTDC1RQr1I6XFrXYi42YNrq6h-f1WLlLE81iddU1NoV3LG-6GPfMgBdimo58ru41XYSZCVVpNvFkBsHrdiRkupc1gzHIIeXwmBE3QvOKWlWLem0dE3tRJLTsuLaVvQm6R3W6lbh5zQr4KSxIxQtfw"],"delay_while_idle":true,"collapse_key":"demo","time_to_live":3000,"data":{"title":"hello","message":"new message","msgcnt":"1"}}' }
{ method: 'POST',
headers:
{ 'Content-Type': 'application/json',
'Content-length': 324,
Authorization: 'key=undefined' },
uri: 'https://android.googleapis.com:443/gcm/send',
body: '{"registration_ids":["APA91bFc7Te2qwTDC1RQr1I6XFrXYi42YNrq6h-f1WLlLE81iddU1NoV3LG-6GPfMgBdimo58ru41XYSZCVVpNvFkBsHrdiRkupc1gzHIIeXwmBE3QvOKWlWLem0dE3tRJLTsuLaVvQm6R3W6lbh5zQr4KSxIxQtfw"],"delay_while_idle":true,"collapse_key":"demo","time_to_live":3000,"data":{"title":"hello","message":"new message","msgcnt":"1"}}' }
{ method: 'POST',
headers:
{ 'Content-Type': 'application/json',
'Content-length': 324,
Authorization: 'key=undefined' },
uri: 'https://android.googleapis.com:443/gcm/send',
body: '{"registration_ids":["APA91bFc7Te2qwTDC1RQr1I6XFrXYi42YNrq6h-f1WLlLE81iddU1NoV3LG-6GPfMgBdimo58ru41XYSZCVVpNvFkBsHrdiRkupc1gzHIIeXwmBE3QvOKWlWLem0dE3tRJLTsuLaVvQm6R3W6lbh5zQr4KSxIxQtfw"],"delay_while_idle":true,"collapse_key":"demo","time_to_live":3000,"data":{"title":"hello","message":"new message","msgcnt":"1"}}' }
{ method: 'POST',
headers:
{ 'Content-Type': 'application/json',
'Content-length': 324,
Authorization: 'key=undefined' },
uri: 'https://android.googleapis.com:443/gcm/send',
body: '{"registration_ids":["APA91bFc7Te2qwTDC1RQr1I6XFrXYi42YNrq6h-f1WLlLE81iddU1NoV3LG-6GPfMgBdimo58ru41XYSZCVVpNvFkBsHrdiRkupc1gzHIIeXwmBE3QvOKWlWLem0dE3tRJLTsuLaVvQm6R3W6lbh5zQr4KSxIxQtfw"],"delay_while_idle":true,"collapse_key":"demo","time_to_live":3000,"data":{"title":"hello","message":"new message","msgcnt":"1"}}' }
Could not send message after 4 attempts
Will node-gcm fail if I send it more than 1000 Registrations ID's in a single message as that is all Google support? Do I have to split it up myself before hand?
Originally by @ghominejad in #92 (but that contained several issues).
Hi,
I know that this would probably be a lame question, but I'm trying to install it on my macbook pro with mountain lion, and I get this response after executing the install command:
npm WARN install Refusing to install node-gcm as a dependency of itself
do you have any idea what could it be?
Thanks,
Juan Docal
In you API i should use addData method. I think it is much simplier to supply json to data property.
Or I am missing something?
Invalid request: 401
/project/server/node_modules/node-gcm/lib/sender.js:60
if (result.results[i].error === 'Unavailable')
^
TypeError: Cannot read property 'results' of undefined
at lambda (/project/server/node_modules/node-gcm/lib/sender.js:60:17)
at IncomingMessage. (/project/server/node_modules/node-gcm/lib/sender.js:129:11)
at IncomingMessage.emit (events.js:88:20)
at HTTPParser.onMessageComplete (http.js:137:23)
at CleartextStream.ondata (http.js:1150:24)
at CleartextStream._push (tls.js:375:27)
at SecurePair.cycle (tls.js:734:20)
at EncryptedStream.write (tls.js:130:13)
at Socket.ondata (stream.js:38:26)
at Socket.emit (events.js:67:17)
I was using this module to send notifications to more than 1k+ users. Till last week i had no problems and it was working perfectly. But this week i couldn't send any notification to users.
Getting a 400 invalid request error without any hint. I know that it's something related with my JSON data format being wrong or so but i can send notification to a single user without any problem.
Anyone experienced something like this?
Here's the message options that i use.
messageOptions = {
collapseKey : "some_key",
delayWhileIdle : false,
timeToLive : 3600*12,
data : {
content : "Some text"
}
}
So my GCM push message works if I use this test link
http://www.androidbegin.com/tutorial/gcm.html
And here's the response I get from that test link:
{ "multicast_id":7724943165862866717,
"success":1,
"failure":0,
"canonical_ids":0,
"results":[{"message_id":"0:1418649384921891% 7fd2b314f9fd7ecd"}]}
However if I push using my own service using node push service using the node-gcm lib, I get a success message on the server but no msg makes it to the client
Here's the success message from the node service:
{ multicast_id: 5130374164465991000,
success: 1,
failure: 0,
canonical_ids: 0,
results: [ { message_id: '0:1418649238305331%7fd2b3145bca2e79' } ] }
How come I'm getting a success message on the server, but no push is received on the client when using the node-gcm library based service. Is there some sort of ip configuration that I need to do, or some sort of certificate? I've used the same google api server key which is open to all ips on both examples.
I'm using the boiler plate code:
var gcm = require('node-gcm');
// create a message with default values
var message = new gcm.Message();
// or with object values
var message = new gcm.Message({
collapseKey: 'demo',
delayWhileIdle: true,
timeToLive: 3,
data: {
key1: 'message1',
key2: 'message2'
}
});
var sender = new gcm.Sender('insert Google Server API Key here');
var registrationIds = ['regId1'];
/**
* Params: message-literal, registrationIds-array, No. of retries, callback-function
**/
sender.send(message, registrationIds, 4, function (err, result) {
console.log(result);
});
Because send function is async , so how wrap it into forEach of array registrationId
I'm on node-gcm latest version and setting the DEBUG flag as a bash variable yields nothing out of the ordinary. Is there something else I need to do asides from setting this flag and installing the npm Debug module?
I've put wrong apiKey and I've expected some error message like unauthorized and all I got in callback is null on both err and result. I think this is bad because client should know what caused an error (is it wrong apiKey or maybe GCM is unavailable or wrong payload is sent). I could make pull request by myself to fix this but first I am asking you what do you think about this.
Also I've seen much console.logs in code which is also bad because people usually don't want to have console.logs in production code, specially from some dependency libraries. You could use some debug library that client can turn on with environment variable like in this module : https://github.com/petkaantonov/bluebird
Ivan
Hi. What is going wrong? Everything seems fine on the server but my device is not receiving anything...
sending google push notification
registrationIds: [ 'xxxxxxxx' ]
result of sending the notification to google: { multicast_id: 6735210410419146000,
success: 1,
failure: 0,
canonical_ids: 0,
results: [ { message_id: '0:1416737849360343%da56e82a002efde3' } ] }
Deprecate Message#addDataWithObject
and Message#addDataWithKeyValue
.
Both of these are now covered by Message#addData
(and will be removed in v1).
They should probably print something to console.warn
or something.
With 0.9.7 I'm occasionally still seeing server hangs on send. It happens very rarely (once in 2-3 weeks). Could it be that in sender.js where the new timeout code (https://github.com/ToothlessGear/node-gcm/blob/master/lib/sender.js#L95) has been added that callback("timeout", null) needs to be called? I'm not sure if aborting the request will cause the error or end events to fire so the callback never gets called, is this possible?
Hi,
var message = new gcm.Message();
var sender = new gcm.Sender('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
var registrationIds = [];
message.addData('title','My Game');
message.addData('message','Your turn!!!!');
message.addData('msgcnt','1');
message.collapseKey = 'demo';
message.delayWhileIdle = true;
message.timeToLive = 3;
registrationIds.push('yyyyyyyyyyyyyyyyyyy');
sender.send(message, registrationIds, 4, function (result) {
console.log('result',result); //return null
});
i can't send notification so can you please find where i miss something?
I have a GCM client on my android device built according to the google tutorial here: http://developer.android.com/guide/google/gcm/demo.html
I'm able to send notifications from the local server from the tutorial, but not from the node-gcm module.
I'm using the sample code in the readme of node-gcm. I've set the senderId and the registrationId exactly the same as in the gcm-demo.
The node-gcm call is successful, however I don't receive the notification on the device.
what might be the issue?
Where should I start looking?
Is there a designated place for such problems other than here?
I am using this behind proxy and have set the environment variables http_proxy and https_proxy. But whenever I try to run the server I get this error:
[Error: tunneling socket could not be established, cause=Parse Error] code: 'ECONNRESET'
Hi,
As far as I can see, there is no support in User Notifications.
Are there any plans in doing so?
Thanks,
GCM can at most take 1000 registration ids at once. If more than this are sent, the call will fail.
This is a documented problem (#82).
It can be solved by automatically chunking the calls, to at most 1000 messages per call.
I registered my domain to Google console, but when send message alway return Unauthorized.
var message = new gcm.Message({
collapseKey: 'demo',
delayWhileIdle: true,
timeToLive: 3,
data: {
key1: 'message1',
key2: 'message2'
}
});
var sender = new gcm.Sender('AIzaSyDglOVD0d5r01bas26xVTqZt*****');
var registrationIds = [];
registrationIds.push('APA91bG9KOMQ2fT1hoqEudiiKk8hF3AqBugi9cruoYR-hDU73xUv2JrVR3LxSXFwfBQ0C1BGofauHR7j98EFENma0wcVHbnOubxL8m0c0L_iU3JZ-2OyVdcqb9dolF7xBi1******');
sender.send(message, registrationIds, 4, function (err, result) {
console.log(result);
});
Now I think restrictedPackageName for message options is important (see https://developer.android.com/reference/com/google/android/gcm/server/Message.html).
I noticed that when calling gcm.send, if there are any unauthorized errors returned from sendNoRetry method, the callback doesn't return the error code properly to the method that is calling the gcm.send.
callback(null, result)- is called but sendNoRetry is sending callback(errorCode, null). How do i handle that?
Currently node-gcm claims to support node 0.6.0 and up:
https://github.com/ToothlessGear/node-gcm/blob/master/package.json#L122
However, because of using the new-style dependencies in package.json, node-gcm will fail on node versions below 0.10.x because its dependencies can't be parsed. The fix would either be to use backwards-compatible dependency version strings or to update the engines requirement to ">= 0.10.0"
.
eg:
hloftis:~/test/node-gcm$ node --version
v0.8.28
hloftis:~/test/node-gcm$ cat package.json | grep gcm
"name": "node-gcm-test",
"node-gcm": "0.9.12"
hloftis:~/test/node-gcm$ npm install
npm WARN package.json [email protected] No README.md file found!
npm http GET https://registry.npmjs.org/node-gcm/0.9.12
npm http 304 https://registry.npmjs.org/node-gcm/0.9.12
npm http GET https://registry.npmjs.org/debug
npm http GET https://registry.npmjs.org/request
npm http 304 https://registry.npmjs.org/debug
npm http 304 https://registry.npmjs.org/request
npm ERR! Error: No compatible version found: debug@'^0.8.1'
npm ERR! Valid install targets:
npm ERR! ["0.0.1","0.1.0","0.2.0","0.3.0","0.4.0","0.4.1","0.5.0","0.6.0","0.7.0","0.7.1","0.7.2","0.7.3","0.7.4","0.8.0","0.8.1","1.0.0","1.0.1","1.0.2","1.0.3","1.0.4"]
npm ERR! at installTargetsError (/usr/local/lib/node_modules/npm/lib/cache.js:679:10)
npm ERR! at /usr/local/lib/node_modules/npm/lib/cache.js:594:10
npm ERR! at saved (/usr/local/lib/node_modules/npm/node_modules/npm-registry-client/lib/get.js:138:7)
npm ERR! at Object.oncomplete (fs.js:302:15)
npm ERR! If you need help, you may report this log at:
npm ERR! <http://github.com/isaacs/npm/issues>
npm ERR! or email it to:
npm ERR! <[email protected]>
npm ERR! System Darwin 13.3.0
npm ERR! command "/usr/local/bin/node" "/usr/local/bin/npm" "install"
npm ERR! cwd /Users/hloftis/test/node-gcm
npm ERR! node -v v0.8.28
npm ERR! npm -v 1.2.12
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR! /Users/hloftis/test/node-gcm/npm-debug.log
npm ERR! not ok code 0
In the lib/sender.js
file you should pass the err
parameter to all the callback
calls inside the Sender.prototype.send
function (lines 113, 116, 143, 147) instead of the null
value.
Otherwise the errors are not propagated to the callback function.
Or are there any side-effects that I do not know about?
Nice work!
The send method is calling my callback twice when an InvalidRegistration error is returned by GCM servers.
First time the result object is set with the expected GCM response. But the second time the callback is called, the error parameter is not set and the result parameter is null.
Also this gets printed in the console:
Error handling GCM response TypeError: Not a buffer
Hello and thanks for your gcm node library.
I encounter this bug pretty often when sending notifications to multiple users at the same time, nine to be exact.
When it occurs, the notification is not delivered to the intended recipients so it might be an error result from google.
Do you have any ideas on how to fix it? In the meantime I've added a debug log to print the response from google services. I'll let you know when I have more.
undefined:1
657.
^
SyntaxError: Unexpected token J
at Object.parse (native)
at IncomingMessage.<anonymous> (/var/www/apns/node_modules/node-gcm/lib/sender.js:123:25)
at IncomingMessage.emit (events.js:88:20)
at HTTPParser.parserOnMessageComplete [as onMessageComplete] (http.js:130:23)
at CleartextStream.socketOnData [as ondata] (http.js:1288:20)
at CleartextStream._push (tls.js:375:27)
at SecurePair.cycle (tls.js:734:20)
at EncryptedStream.write (tls.js:130:13)
at Socket.ondata (stream.js:38:26)
at Socket.emit (events.js:67:17)
Not sure it is related but I'm using node v0.6.18
In the current implementation, the format of the response object depends on whether the request was unicast (sent to just one device) or multicast (sent to multiple devices).
See this.
The approach is extremely inconvenient (at least for myself), because it requires different handling procedures depending on the number of registrationIds
passed.
I'm sending messages to "all" devices and in some cases "all" means "just one" - I'd hate to have a special case here.
I'd suggest one of the options:
As the issue is a show-stopper for me, I had to "implement" the second option like this: laggyluke/node-gcm@f8b046901ab4acc3e5b81c18c541aa19b0c01b8c.
It looks like the send() method would always be short one retry if you have multiple registrations ids.
https://github.com/ToothlessGear/node-gcm/blob/master/lib/sender.js#L134
if (attempt < retries - 1) {
If retries is 0, it bypasses the logic to check what happened and just fires the callback with the error and result passed right in. But it looks like it will also be skipping past any retry if retry is set to one. Am I missing something here? Thus, no retry even though there should be one.
Originally by @ghominejad:
If we have some unsents then we have loss the callback results of sent items. consider we have only 1 Unavailable and 99 success items. then it retries to send one unsent and after finishes we have only one result in callback results.
https://github.com/ToothlessGear/node-gcm/blob/master/lib/sender.js#L154
The solution to this should maintain the order of the registration ids even when some fail, and return the full result set after the given number of retries.
Error message is below:
undefined:1
^
SyntaxError: Unexpected token F
at Object.parse (native)
at IncomingMessage. (/mnt/hgfs/zly/eclipseWsPhpLocal/jsprj1/nodejs/the3rdPart/gcm/t1/sender.js:122:27)
at IncomingMessage.emit (events.js:88:20)
at HTTPParser.onMessageComplete (http.js:137:23)
at CleartextStream.ondata (http.js:1124:24)
at CleartextStream._push (tls.js:363:27)
at SecurePair.cycle (tls.js:685:20)
at EncryptedStream.write (tls.js:122:13)
at Socket.ondata (stream.js:38:26)
at Socket.emit (events.js:67:17)
I added log, found it was "gcm https ret data:Field "delay_while_idle" must be a JSON boolean: 1".
I have fixed it . codes parts are below:
part 1:
if (message.delayWhileIdle !== undefined) {
body[Constants.PARAM_DELAY_WHILE_IDLE] = message.delayWhileIdle;// message.delayWhileIdle ? '1' : '0';
}
part 2:
in sendNoRetryMethod function in sender.js,
var post_req = https.request(post_options, function(res) {
res.setEncoding('utf-8');
var statusCode = res.statusCode;
var buf = '';
res.on('data', function (data) {
buf += data;
});
res.on('end', function () {
console.log("gcm https ret data:"+buf);
var data = JSON.parse(buf);
if (statusCode === 503) {
console.log('GCM service is unavailable');
callback();
}
else if (statusCode !== 200) {
console.log('Invalid request: ' + statusCode);
callback();
}
if (registrationIds.length === 1) {
if(data.results[0].message_id)
result.messageId = data.results[0].message_id;
else if(data.results[0].error)
result.errorCode = data.results[0].error;
else if(data.results[0].registration_id)
result.canonicalRegistrationId = data.results[0].registration_id;
callback(result);
}
else callback(data);
});
});
Using 0.99. The server somehow gets into a weird state where all requests return null error and result and no GCM messages are sent. Not sure why. One of my servers was running fine for over a week with no issues then this started happening today.
I need to send some important news to more than 100000 Registrations ID's. why do i need to keep 100000 Ids? Is there a better solution?
result value Success? Failure?
sorry .. I am not good at speaking english. ใ
_ใ
help me...
:: result ====================
{ messageId: '0:xxxxxxx,
canonicalRegistrationId:
undefined, errorCode: undefined }
canonicalRegistrationId: undefined
errorCode undefined
:: other result ====================
{"multicast_id":6087232423423780029729,"success":1,"failure":0,"canonical_ids":
0,"results":[{"message_id":"0:134123442653371%846851f8cf9fd7ecd"} ]}
:: Questions ====================
Results that have duplicate value regId How do I find it regId?
https://developer.android.com/google/gcm/http.html#response
results
in the response may contain an error. This should be parsed and should be returned as an error for the call (the call fails!), not as data.
This would break the interface and should be made as part of a major version bump.
How can i monitor GCM Service using my API key through Nagios / NRPE ?
When sending a message, it would be nice to be able to pass only a single device-id. This changes the error-handling a bit, as context changes, too: Instead of failing only if GCM returns an error, it should now also fail if the message was not delivered (error in the results array).
I'm getting my registration ID from the android app I created, and pasting that into the "registration ID". I'm also grabbing my API key and pasting that into gcm.sender. For some reason it keeps returning "MismatchSenderID" What could I be doing wrong?
Hi. I'm getting 400
error code and trying to figure out what's wrong
here's my code :
var sender = new gcm.Sender(gcm_api_key);
obj.push_android = function(gcm_regid, payload, callback) {
var message = new gcm.Message();
var reg_ids = [];
message.addDataWithKeyValue('key1','message1');
message.timeToLive = Math.floor(Date.now() / 1000) + 3600 * 12; // expires 12 hour from now
message.collapseKey = 'myapp';
message.delayWhileIdle = false;
reg_ids.push(gcm_regid);
sender.send(message, reg_ids, 4, function (err, result) {
if (err) return callback(err);
return callback();
});
};
Do you have an idea what could be wrong ?
Thanks for the module !
Right now, the number of retries for Sender's send
method has to be set manually. A reasonable default (5) would be nice.
I always get the success message, but my phone never receives a notification. Here's my code
var regId = "XXXX";
var gcm = require('node-gcm');
// create a message with default values
var message = new gcm.Message();
// or with object values
var message = new gcm.Message({
collapseKey: 'demo',
timeToLive: 3,
data: {
key1: 'message1',
key2: 'message2'
}
});
var sender = new gcm.Sender('AIzaSyDIWlkp-Z5xvjpR8cLtg5_-MgcUI_7HRPs');
var registrationIds = [];
// At least one required
registrationIds.push(regId);
sender.send(message, registrationIds, 4, function (err, result) {
console.log(result);
});
Thanks
Publish project in npm registry and add Install section to README.md
Invalid request: 502
TypeError: Cannot read property 'results' of null
at lambda (/projects/server/node_modules/node-gcm/lib/sender.js:130:31)
at IncomingMessage.Sender.sendNoRetry (/projects/server/node_modules/node-gcm/lib/sender.js:69:24)
at IncomingMessage.EventEmitter.emit (events.js:126:20)
at IncomingMessage._emitEnd (http.js:366:10)
at HTTPParser.parserOnMessageComplete as onMessageComplete
at CleartextStream.socketOnData as ondata
at CleartextStream.CryptoStream._push (tls.js:544:27)
at SecurePair.cycle (tls.js:898:20)
at EncryptedStream.CryptoStream.write (tls.js:285:13)
at Socket.ondata (stream.js:38:26)
I'm looking at the check on line 109 of sender.js:
https://github.com/ToothlessGear/node-gcm/blob/master/lib/sender.js#L109
and I'm wondering how it ever retries as sendNoRetry always calls the callback with result of null when an error occurs. The check then becomes if (null === undefined) which evaluates to false. Should it not just be if (result == undefined) or (result == null) or just if (!result)?
I think there are some serious problems in the codes.
1 - Consider you have 100 registerIds and 50 of them are unsents. so is this code correct?
// registrationId.length is 100 items
for (i = 0; i < registrationId.length; i += 1) {
// result.results.length is same as unsents length (50 items)
if (result.results[i].error === 'Unavailable') {
unsentRegIds.push(registrationId[i]);
}
}
https://github.com/ToothlessGear/node-gcm/blob/master/lib/sender.js#L132-L136
2 - If we have some unsents then we have loss the callback results of sent items. consider we have only 1 Unavailable and 99 success items. then it retries to send one unsent and after finishes we have only one result in callback results.
https://github.com/ToothlessGear/node-gcm/blob/master/lib/sender.js#L154
3- In gcm spec we should respect Retry-After header
It seems misleading that the reference to the data object is overwritten with whatever is given to addDataWithObject
.
A more sensical way to do it, would be to merge the two objects, overwriting any values that were already present.
This would allow known values to be set at time of construction, and later values to be added after:
var message = new gcm.Message({
data: {
knownValue: "atConstructionTime",
otherValue: "thatMayChangeBeforeSending"
}
});
//State: { knownValue: "atConstructionTime", otherValue: "thatMayChangeBeforeSending" }
message.addData({
newValue: "thatIsOnlyAddedLater"
});
//State: { knownValue: "atConstructionTime", otherValue: "thatMayChangeBeforeSending", newValue: "thatIsOnlyAddedLater" }
if(something) {
message.addData({
otherValue: "thatHasNowBeenChanged"
});
//State: { knownValue: "atConstructionTime", otherValue: "thatHasNowBeenChanged", newValue: "thatIsOnlyAddedLater" }
}
Currently, if an invalid request is made, all that is returned is the error code. The debug message (when running with DEBUG=node-gcm
) is not any more helpful.
For example, I was trying to send a notification contianing a key from
. This happens to be a reserved keyword. I had no way of knowing this, because all I was told was Invalid request 400
.
A better model for the callbacks on error would be returning the body as well as the status-code:
} else if (res.statusCode !== 200) {
debug('Invalid request: ' + res.statusCode);
return callback({
status: res.statusCode
response: resBody
}, null);
}
(Modified version of lines
Lines 89 to 92 in 2a293a6
In my case, this gave me: '"data" key "from" is a reserved keyword\n'
--- exactly what I needed to know.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.