sideshow / apns2 Goto Github PK
View Code? Open in Web Editor NEW⚡ HTTP/2 Apple Push Notification Service (APNs) push provider for Go — Send push notifications to iOS, tvOS, Safari and OSX apps, using the APNs HTTP/2 protocol.
License: MIT License
⚡ HTTP/2 Apple Push Notification Service (APNs) push provider for Go — Send push notifications to iOS, tvOS, Safari and OSX apps, using the APNs HTTP/2 protocol.
License: MIT License
The delay is too long for me while pushing notifications one by one, i am seeking for a way that could push a batch of notifications in a time, is there any suggestions for me?
It would be great if there were detailed install instructions. What is included in the readme is not clear.
What am I supposed to do after running the go get commands?
Hi:
when I push apns notice, it return dial udp 114.114.114.114:53: i/o timeout. I think the APNS2 can't connect the Apple's server. How can I fix it?
Below is the error:
[16:45:44 CST 2016/09/06] EROR Error:%!(EXTRA *url.Error=Post https://api.development.push.apple.com/3/device/3f8b4ecbae42914c2aa84300d300f8d2f55f5c7f96ea3770846f0c011c
07a8a5: dial tcp: lookup api.development.push.apple.com on 114.114.114.114:53: dial udp 114.114.114.114:53: i/o timeout)
[16:45:44 CST 2016/09/06] INFO client res: %!(EXTRA *apns2.Response=)
[16:45:46 CST 2016/09/06] EROR push again Error: %!(EXTRA *url.Error=Post https://api.development.push.apple.com/3/device/3f8b4ecbae42914c2aa84300d300f8d2f55f5c7f96ea3770846f0c011c
07a8a5: dial tcp: lookup api.development.push.apple.com on 114.114.114.114:53: dial udp 114.114.114.114:53: i/o timeout)
[16:45:46 CST 2016/09/06] EROR push again fail
Only the first build the push connection has this error.
here is mine code:
func pushApnsAndSaveException(message []byte, client *apns.Client) {
pMsg := pMessage.PushMessage{}
json.Unmarshal(message, &pMsg)
payload := NewPayload()
payload.Alert(pMsg.Content)
payload.Badge(pMsg.Count)
payload.Custom("id", pMsg.Id)
payload.Custom("type", pMsg.Type)
payload.Custom("contentType", pMsg.ContentType)
payload.Sound("sms-received1.caf")
payloadByte, err := json.Marshal(payload)
if err != nil {
log.Println("json marshal error:", err)
}
log.Println("payload json: ", string(payloadByte))
notification := &apns.Notification{}
notification.DeviceToken = pMsg.Token
notification.Topic = apnsConst.ApnsTopic
notification.Priority = 10
notification.Payload = payloadByte
l4g.Info("payload btye: ", string(payloadByte), ",payload: ", notification.Payload)
res, err := client.Push(notification)
.........
}
the variable client *apns.Client is global.
run demo, throw error:
Error:net/http: Client Transport of type *http2.Transport doesn't support CancelRequest; Timeout not supported
when do apns2 support golang 1.6 ?
Unless I'm misunderstanding something, it would make sense to provide a client pool for APNS connections. It would be helpful for those who use this lib with multiple certificates.
My understanding is the ClientConnPool in the http2 libs cannot be used because the underlying Transport
is used across all clients in the pool. That wouldn't work for this lib, as the TLS connection is configured with the Apple certificate for each client's transport.
If this is deemed a good idea and a possibility, I'd be willing to start a PR.
hello, i am trying to get notification on my ios app,
apns2 is working fine like this. but my app is not getting notification.
2016/11/27 13:00:12 APNs ID: BA5AED23-31F3-77A6-9E8E-C9C0274ACEE0
of course, I did all things correctly on app side.
I checked my app with other php sample. this is php sample(apns not apns2) url link.
https://www.raywenderlich.com/123862/push-notifications-tutorial
You use
HostDevelopment = "https://api.development.push.apple.com"
HostProduction = "https://api.push.apple.com"
But he uses (php sample)
ssl://gateway.sandbox.push.apple.com:2195
How will I do?
My golang version is 1.7.3
cert.pem is Apple Developer IOS Push Services cert file.
OS: windows 10
Comparing to current https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1 these are missing (probably only first is valid for this package):
BadCollapseId
ExpiredProviderToken
InvalidProviderToken
MissingProviderToken
TooManyProviderTokenUpdates
in my program, i made 1000 goroutines for concurrency send msg to apple; when i check my log, i find a lot of error returns when i call client.Push, the error is "net/http: request canceled (Client.Timeout exceeded while awaiting headers)"; the timeout setting of the client is 10s:client.HTTPClient.Timeout = time.Duration(10) * time.Second;
i have no idea why this happened, is the timeout setting too short?
Is the client safe for use from concurrent goroutines?
I write my push
operation according with the demo in ReadMe.md, but i am confused with the follwings:
client := apns2.NewClient(cert)
...
res, err := client.Push(notification)
so, when the connection was builded? when the DialTLS (client.HTTPClient.Transport.(*http2.Transport).DialTLS("tcp", "127.0.0:1", nil)) was called ?
Hi, I saw in README that this library support Pushkit, but I cannot find the implementation as well as the base url of gateway.push.apple.com for Pushkit send notification. Is there any example or explanation regarding the pushkit implementation?
client.Production()
/ client.Development()
on clients return from ClientManager.Getclient.Production()
and apns2/client.go:156 (url := fmt.Sprintf("%v/3/device/%v", c.Host, n.DeviceToken)
)It would be great to support testing with a mock apn server that does not use ssl.
As @jchambers picked up here #41, the APNs thread-id should be part of the payload and not a header as currently implemented.
I have a daemon process that just forwards push notifications to apple using apns2.
A handful of times when the process has started no push notifications were sent to apple. Restarting the apns2 daemon process fixes this. The contents of the apns2 successful and unsuccessful requests are exactly the same.
During the time when delivery of these push notifications failed the devices receive other push notifications. Also during this time the devices are able to directly communicate with the host that the apns2 daemon is running on.
Is there any way to enable detailed trace logging of apns2's http2 communications? For example, was it able to tcp connect, negotiate ssl, or actually send the http request?
Is there any way to enable timeouts of its requests or connection attempts?
strconv.ParseInt: parsing "1469425701606": value out of range
The error above happended sometime when i call client.Push(notification). Please help.
Use '_example / main.go', run returns an error
2016/05/31 15:39:42 Error: Post https://api.development.push.apple.com/3/device/269ccfa053618d819aa4e7150455e04163bf6b0f9d598affc9a596b7d215222f: remote error: error decrypting message
Version go1.6.2
Please ensure that you are using the latest version of the master branch before filing an issue.
Also make sure to include logs (if applicable) to help reproduce the issue by setting the GODEBUG=http2debug=2
env var.
go version
go env
hi.
when I use it push message to apple's push server, it response stautscode=200, means push successed. but sometings my iphone can't received the push message.
why not 100% received push message by iphone. it's normal or is bug.
it,s my logs.
[2016/09/01 20:47:03 CST] INFO OK APNs ID:%!(EXTRA string=A9D9C902-0565-75D2-8A43-8F8BB92C1297, string=,StatusCode:, int=200, string=,Reason:, string=, string=,Timestamp:, apns2.Time=0001-01-01 00:00:00 +0000 UTC)
Hi,when i execute this code(res, err := client.Push(notification)), it cost about 1~3 seconds. but i want push more(probably push 1000 per second). how can i improve the performance. Thanks
package main
import "fmt"
import "github.com/sideshow/apns2/certificate"
import "os"
func main() {
apnsCert, err := certificate.FromPemFile(os.Args[1], "")
if err != nil {
fmt.Printf("Unable to read your certificate: %v\n", err)
} else {
fmt.Printf("Parsed certificate: %v\n", apnsCert)
}
}
openssl pkcs12 -in Certificates.p12 -out testcertificate.pem -nodes -clcerts
go run main.go testcertificate.pem
A parsed certificate.
$ go run main.go testcertificate.pem
Error parsing private key asn1: structure error: tags don't match (2 vs {class:0 tag:16 length:13 isCompound:true}) {optional:false explicit:false application:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} @5
Unable to read your certificate: failed to parse PKCS1 private key
$ go version
go version go1.9.1 darwin/amd64
I opened a Stack Overflow question attempting to resolve this. The issue appears to be that sideshow/apns2/certificate
assumes the private key to be in PKCS#1 format, whereas openssl pkcs12
generates a file with a private key in PKCS#8 format. I suggest that this lib should be able to parse PKCS#8 format, too.
Please ensure that you are using the latest version of the master branch before filing an issue.
Also make sure to include logs (if applicable) to help reproduce the issue by setting the GODEBUG=http2debug=2
env var.
What version of Go are you using? go version
1.9.2
What OS and processor architecture are you using? go env
linux amd64
What did you do?
Here's my code simulate the situation which reach the MaxAge
:
cert, _ := certificate.FromPemFile("cert.pem", "")
manager := apns2.NewClientManager()
manager.MaxAge = time.Second * 5
for {
resp, err := manager.Get(cert).Production().Push(&apns2.Notification{Topic: apnsTopic, DeviceToken: deviceToken, Payload: payload})
fmt.Println(resp, err)
time.Sleep(time.Second * 6)
}
What did you expect to see?
ClientManager create a new client, and the underlying connections of old client should be closed by GC or call client.CloseIdleConnections().
What did you see instead?
netstat
and lsof
shows that number of open connections keep increasing.
Maybe ClientManager should close idle connections on removing old client?
If do this so, what if another goroutine using the same client while we call client.CloseIdleConnections()?
The default golang http2 connection pool is good but lacks some important features which would be useful for the apns2 library:
Ideally we want to write a custom connection pool to address the above issues. It should have reasonable defaults so that no configuration is needed out of the box, but provide the correct settings to customize for those that are using apns2 at scale.
Please ensure that you are using the latest version of the master branch before filing an issue.
Also make sure to include logs (if applicable) to help reproduce the issue by setting the GODEBUG=http2debug=2
env var.
go version
go env
How can I prevent this error?
As a preface, I'm reporting this to open a dialog. I think the ultimate problem will be with something I'm doing or a bug deep within the http2 libs of Go.
I've noticed that occasionally the call to c.HTTPClient.Do
hangs indefinitely. The problem occurs intermittently and seemingly not because of the certificate used in the connection. Given enough retries (where the connection is remade by making a new handle on apns2.Client
), it will succeed without error.
I'm not convinced this is a network issue. It seems something is deadlocking within the http2 libs. I set c.HTTPClient.Timeout
to 1 second, which never triggers. Additionally I spin up a timeout goroutine of 3 seconds, which is how I determine that something is hanging, and at which point I attempt a retry. (As a side note, I just realized this may not cleanly kill the connection. Perhaps I should call CloseIdleConnections
on the http2 transport?) It doesn't seem to be a network issue. Despite setting GODEBUG=http2debug=2
, no http2 logs are outputted.
My sender is massively concurrent, having thousands of goroutines at any moment, but my interpretation is that if it gets to c.HTTPClient.Do
, which is documented to be thread-safe, and then hangs, then there is a problem within the http2 libs.
Am I weirdly running out of possible connections or something? Does anyone have thoughts on this?
Is there any feedback channel support for push notification ?
Hi,
Is this library support apple voip push? If yes, can you provide us example how to send this kind of notification?
Thanks !
hi, when ios app is reinstalled and device token is changed . but client.Push()
push old‘s device token returned 200 status code ? thanks.
A new release which includes ClientManager would be very welcome :)
Also including the PRs about data races
client manager has the data race on m.initInternals
which is not located in the critical section 🔥
How you can find it:
diff --git a/client_manager_test.go b/client_manager_test.go
index 5e5d290..e6e372d 100644
--- a/client_manager_test.go
+++ b/client_manager_test.go
@@ -4,6 +4,7 @@ import (
"bytes"
"crypto/tls"
"reflect"
+ "sync"
"testing"
"time"
@@ -35,14 +36,22 @@ func TestClientManagerGetWithoutNew(t *testing.T) {
}
func TestClientManagerAddWithoutNew(t *testing.T) {
+ wg := sync.WaitGroup{}
manager := apns2.ClientManager{
- MaxSize: 32,
+ MaxSize: 1,
MaxAge: 5 * time.Minute,
Factory: apns2.NewClient,
}
- manager.Add(apns2.NewClient(mockCert()))
- assert.Equal(t, 1, manager.Len())
+ for i := 0; i < 2; i++ {
+ wg.Add(1)
+ go func() {
+ manager.Add(apns2.NewClient(mockCert()))
+ assert.Equal(t, 1, manager.Len())
+ wg.Done()
+ }()
+ }
+ wg.Wait()
}
func TestClientManagerLenWithoutNew(t *testing.T) {
$ go test -race -v github.com/sideshow/apns2 -run ^TestClientManagerAddWithoutNew$
=== RUN TestClientManagerAddWithoutNew
==================
WARNING: DATA RACE
Read at 0x00c420077578 by goroutine 8:
github.com/sideshow/apns2.(*ClientManager).Add()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager.go:63 +0x62
github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew.func1()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:59 +0xc3
Previous write at 0x00c420077578 by goroutine 7:
github.com/sideshow/apns2.(*ClientManager).Add()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager.go:64 +0x6eb
github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew.func1()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:59 +0xc3
Goroutine 8 (running) created at:
github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:62 +0x168
testing.tRunner()
/usr/local/Cellar/go/1.7.3/libexec/src/testing/testing.go:610 +0xc9
Goroutine 7 (running) created at:
github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:62 +0x168
testing.tRunner()
/usr/local/Cellar/go/1.7.3/libexec/src/testing/testing.go:610 +0xc9
==================
==================
WARNING: DATA RACE
Write at 0x00c420077588 by goroutine 8:
sync/atomic.CompareAndSwapInt32()
/usr/local/Cellar/go/1.7.3/libexec/src/runtime/race_amd64.s:293 +0xb
sync.(*Mutex).Lock()
/usr/local/Cellar/go/1.7.3/libexec/src/sync/mutex.go:46 +0x4d
github.com/sideshow/apns2.(*ClientManager).Add()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager.go:66 +0x8c
github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew.func1()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:59 +0xc3
Previous write at 0x00c420077588 by goroutine 7:
github.com/sideshow/apns2.(*ClientManager).Add()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager.go:64 +0x7f4
github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew.func1()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:59 +0xc3
Goroutine 8 (running) created at:
github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:62 +0x168
testing.tRunner()
/usr/local/Cellar/go/1.7.3/libexec/src/testing/testing.go:610 +0xc9
Goroutine 7 (finished) created at:
github.com/sideshow/apns2_test.TestClientManagerAddWithoutNew()
/Users/xjewer/www/go/src/github.com/sideshow/apns2/client_manager_test.go:62 +0x168
testing.tRunner()
/usr/local/Cellar/go/1.7.3/libexec/src/testing/testing.go:610 +0xc9
==================
--- PASS: TestClientManagerAddWithoutNew (0.00s)
PASS
Found 2 data race(s)
exit status 66
FAIL github.com/sideshow/apns2 1.029s
Hello there!
Are there plans to add the newly released Token Authentication to the library? If there is work already in progress, when can we expect it to be merged into master and if not, will a PR be welcome?
Hi,
Setup as per your readme, substituting notification.DeviceToken and notification.Topic for my own.
I'm getting the following error:
2016/12/11 10:28:39 Error:Post https://api.push.apple.com/3/device/<my_hex_id_goes_here> : http2: Transport: peer server initiated graceful shutdown after some of Request.Body was written; define Request.GetBody to avoid this error
If it makes any difference, this is with go version go1.7.4 linux/amd64
I am porting old apns(github.com/Coccodrillo/apns) to your package.
Coccodrillo/apns has implemented a function to check whether payload is exceed for different IOS version.
prior to IOS8 : maximum payload is 256
Hi,
I'm trying apns2 to manage push notification on my iOS app, but i get this response :
Reason: MissingProviderToken
Status: 403
what did I forget?
var (
Certificate tls.Certificate
)
func InitClient(){
Certificate, _ = certificate.FromPemFile("./cert.pem", "")
}
func SendNotificationIOS(alert string, deviceToken string) bool {
notification := &apns.Notification{}
notification.DeviceToken = deviceToken
notification.Topic = "BUNDLE_IDENTIFIER"
notification.Payload = []byte(`{"aps":{"alert":"Hello!"}}`)
client := apns.NewClient(Certificate).
res, err := client.Push(notification)
fmt.Println("APNs ID: ", res.ApnsID)
fmt.Println("Reason: ", res.Reason)
fmt.Println("Status: ", res.StatusCode)
if err != nil {
return false
}
return true
}
ClientManager
is a great class, it would be nice to have a version of this class (TokenClientManager
?) which works for JWT/Auth token based clients. It looks like this could be done without too much effort (make the Get
method take a *token.Token
, change cacheKey
to be based on *token.Token
instead of tls.Certificate
, make Factory
take a *token.Token
instead of a tls.Certificate
).
Please ensure that you are using the latest version of the master branch before filing an issue.
Also make sure to include logs (if applicable) to help reproduce the issue by setting the GODEBUG=http2debug=2
env var.
What version of Go are you using? go version
go version go1.8.3 windows/amd64
What OS and processor architecture are you using? go env
windows amd64
What did you do?
$ go get github.com/sideshow/apns2/apns2
$ apns2 --help
bash: apns2: command not found
What did you expect to see?
apns2 help section
What did you see instead?
apns2
not found.
First time using go
. I expected that go get <>
would install package and would add executable to path.
Update
I have found that C:\Users\<myUser>\go\bin
contains the apns2.exe
. I have added this to the path, and its working
$ apns2
apns2.exe: error: required flag --certificate-path not provided, try --help
I was argueing with my colleague wheather I can write code like that
client = apns.NewClient(...)
for i := array {
go sendpush(client)
}
Can I send push req without lock?
where is the mechanism that make sure the validation of http2 frame on that tcp connection? i am looking for details but haven't got yet
Can clients be used from multiple goroutines?
Is it recommended to keep an instance of client laying around or just create a new one per batch?
In payload/builder.go:
type aps struct {
...
ThreadID string `json:"thread-id,omitempty"`
}
func (p *Payload) ThreadID(id threadID) *Payload {
p.aps().ThreadID = threadID
return p
}
We can send android push notification through proxy using following
transport := &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
client := &http.Client{Transport: transport}
sender := &gcm.Sender{ApiKey: viper.GetString("pushNotification.gcmAPIKey"),Http:client}
but apns2 is using http2 and http2 transport doesn't have any way to set the proxyUrl.
Is it possible to send apple push notification through proxy server?
When you try to send data with an invalid certificate, you get this error:
Post https://api.push.apple.com/3/device/_token_goes_here_: http2: Transport: peer server initiated graceful shutdown after some of Request.Body was written; define Request.GetBody to avoid this error
This makes it look like a connection or implementation issue, while it is not.
When looking at the http2 logs, you can see that the gateway is explicitly telling you why they are cutting connection, the certificate is wrong: APNsConnectionCut.txt. The interesting part is: http2: Transport received GOAWAY len=46 LastStreamID=0 ErrCode=NO_ERROR Debug="{"reason":"BadCertificateEnvironment"}"
While it would have been nice from the Gateway to use the usual status error code to tell that the certificate is wrong, it does not. If there is an easy way to read GOAWAY frames to return an error that includes the eventual Debug field from Transport, it would be great to use it. If not, you may archive this report.
Hello,
I am trying to use the apns2 module but when I try to load the p12 file I get the following error
ans1: indefinite length found (not DER)
I was able to convert the file to .pem
using the command line openssl, and it worked but this is not an ideal solution, I need to be able to use the p12 file directly or convert it to pem at runtime. Sadly the openssl bindings do not offer this functionality as of now. Any suggestions?
Hello. Thank you for the lib.
I use this command to convert p12 cer to pem
openssl pkcs12 -in aps.p12 -out aps.pem -nodes -clcerts
Which is works fine when i test it with ruby houston gem.
To test this lib i just use example code from readme, only path to the pem file is changed.
The result is error message
Cert Error:failed to parse PKCS1 private key
Which is referenced to apns2/certificate/certificate.go
from this lib.
When it fail i tried to send passphrase as the last param into certificate.FromPemFile
without success.
My questions are:
Thanks in advance.
go version go1.6.2 darwin/amd64
When i say works fine i mean push notifications are delivered to destination device.
apns2 has been working great in production, for the most part. However, occasionally, I see inexplicable "unexpected EOF" errors. The error is a &url.Error{Op:"Post", URL:"https://api.push.apple.com/3/device/<omitted>", Err:(*errors.errorString)(0xc82000a150)}
, where the Err
property of the url error is "unexpected EOF". The error is not a temporary url.Error, so I assumed it may be an issue with my apns2.Client
's connection, which I am keeping open. So when I encounter the error, I remake the client (thus remaking the connection), but the error continues to happen (makes sense, it's a permanent error). I'm just not sure what it could be, exactly.
Any ideas for what this means or how I could debug?
Using apns2 a6f9928377a5bf0f0605e3410f2a13f286f7a5a9
.
when i create a new client,how can i check health of it ?
In Apple Privider Api Doc, it said: "You can check the health of your connection using an HTTP/2 PING frame." In your "Push" fuction, you said:
"If the underlying http.Client is not currently connected, this method will attempt to reconnect transparently before sending the notification."
so, did i need do something else to keep alive of the connetion?
https://github.com/sideshow/apns2#jwt-token-example
my code
func Push2User() {
authKey, err := token.AuthKeyFromFile("/root/xxxxx/AuthKey_6737ABXXXX.p8")
if err != nil {
log.Fatal("token error:", err)
}
token := &token.Token{
AuthKey: authKey,
KeyID: "6737ABXXXX",
TeamID: "MNLVE7XXXX",
}
notification := &apns2.Notification{}
notification.DeviceToken = "80F3C8FAF0DC85144D192075D2A884FAACC9492A219C90C6CF6FB7460128A7E0"
notification.Topic = "com.xxxx.xxxx.io"
payload := payload.NewPayload().Alert("Hello World").Badge(1).Custom("url", "http://xxxx.co")
notification.Payload = payload
log.Println(notification)
// client := apns2.NewTokenClient(token).Production()
log.Println(token)
client := apns2.NewTokenClient(token).Development()
res, err := client.Push(notification)
if err != nil {
log.Fatal("Error:", err)
}
fmt.Printf("%v %v %v\n", res.StatusCode, res.ApnsID, res.Reason)
}
iOS 10 Adds another key you can set on the payload, mutable-content
which can be set to 0 or 1 based on if you wish to intercept the payload and mutate or replace it before the user sees it.
Will need to expand apsn2 to include that and make sure it's support via CLI and payload builder.
I'll put in a PR for this, to follow in the next few days.
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.