saljam / webwormhole Goto Github PK
View Code? Open in Web Editor NEWPeer authenticated WebRTC.
License: BSD 3-Clause "New" or "Revised" License
Peer authenticated WebRTC.
License: BSD 3-Clause "New" or "Revised" License
We could run a bunch of net namespaces and simulate various NAT setups between them to test transfer success rate.
Testing Go client would be great. Bonus points if it tested browsers too.
Currently the sending side just calls datachannel.send() on an arraybuffer holding the entire file. Support for streaming large files (#4) aside, this only works on non-tiny files in Firefox.
MDN says:
Messages smaller than 16kiB can be sent without concern, as all major user agents handle them the same way. Beyond that, things get more complicated.
We should do what that says and chunk our sends.
the current web interface is a little too calm and bright. obviously we need dark mode css.
can we add a dark mode for the command line tool too?
For example, I write code on laptop, and run ww sync ./dir
and on server side another instance of ww syncing files. Ofc, ww would need to watch files for changes and send them. A 2 way sync would be great, but unidirectional can be Ok as well.
Thanks for this great piece of code
First, this is really cool! Thanks so much for your efforts on it!
Trying to test sending from local Linux system (via ww cmdline) to ChromeOS device via the web interface, and it doesn't seem to ever connect. Same issue trying to receive on the Linux system via cmdline.
Oddly, my phone (Android w/ Chrome using the web interface) transfers ok between both the Linux system cmdline and the ChromeOS in both directions.
Not sure what the best approach is to debug this, as I'm not seeing any obvious errors.
it would be nice to figure out a way to resume transfers.
maybe after sending a file metadata, wait for the other side to send an "offset" reply. in browsers, a service worker on the receive side can translate the Range header to the offset.
When using the WebWormhole Firefox app, the URL of the new wormhole is not present in the pop-out window. It then becomes a task to type out the URL and append the Unique ID to the end. Instead, there should be a button that copies the URL of the new wormhole to the clipboard for easy paste into an email or chat.
Thanks.
While the trust model does not include the signalling server, it does include wherever the web interface came from. If our "official" instance ever gets compromised it can e.g. inject malicious javascript and we don't want that. We we need a way to run "known trusted" versions of the web UI.
You can't just save the html/js files to disk and open that. Wasm and JS modules can't be loaded from file:// URLs.
Maybe we can encode the wasm as base64 and stick that in index.html?
Maybe embedding the webui in the signalling server is enough? We can give it an option to choose whether to use the local instance for signalling or the default one.
Hi! First of all, I love the tool :)
Now to the question, I'm reading the source code to understand how it all works and I don't follow the Wormhole.addCandidates
code:
Line 251 in 36a8496
writeEncJSON
with the candidate data?
Let me know if opening an issue is not the best medium for this kind of questions.
Why wait for the user to click "Connect"?
But if the user wants to join an existing wormhole we'll need to figure how to let them override that.
Since successful STUN-mediated NAT traversal requires at least one side of the connection to be on something better than a symmetric NAT, how about a visual indicator for this? Heuristic detection of the NAT type seems to be possible in WebRTC: https://webrtchacks.com/symmetric-nat/
How about something like a traffic light, yellow for symmetric NAT or worse, green for a traversable NAT, and maybe red for blocked UDP (assuming this is detectable by a STUN server not being reachable at all in the test)?
It would not be 100% accurate (IPv6 paths can be different from v4 and possibly many other reasons), but the semantics would be roughly that for two users with yellow lights, or one with a red light, there will probably not be a P2P connection, saving everybody some time and frustration.
Please consider writing about
as they are involved in the connection.
My browser is quite restricted, e.g. WebRTC is disabled.
I see an error on the browser console that RTCPeerConnection
is not defined.
Could you check the availability of WebRTC before using it?
Thanks.
Is it possible to run this with a simple docker-compose.yml as a microservice?
Or is there any reason why the project is tightly coupled with linuxkit world?
Thx
I'd imagine each file transfer creates a new datachannel. First message is a JSON encoded header, then the data chunked across a bunch of messages, then a hash.
I'm thinking a JSON trailer, like the JSON header, that has a sha256 sum of the file sent.
It appears that webwormhole does not work with internet explorer. If you navigate to webwormhole.io in IE you do not get the "new wormhole option" it just says "loading" on the button. If you enter a code, the button also does not allow you to submit.
I've a connection problem but only when on different networks: Tried between my laptop (Debian linux, chrome) and my mobile (Android, Chrome): when I am connected with both devices to the same wifi, it works, but not when I set the mobile to LTE (O2 German) only. Happens both direction.
Interestingly, I also tried to deploy webwormhole on a local NAS which is accessible via a CNAME + port forwarding on the router + a reverse proxy (traefik) from the outside, and there it's the same (both on wifi: works; mobile outside: broken in both directions). The only thing I see on the log is
webwormhole | 2020/05/10 02:33:07 4 book
webwormhole | 2020/05/10 02:33:33 4 visit
webwormhole | 2020/05/10 02:33:33 4 rendezvous
When I try https://test.webrtc.org/ while on the mobile network, then this gives me an error in the connectivity part:
Reflexive connectivity
[ INFO ] Gathered candidate of Type: srflx Protocol: udp Address: 46.114.39.xx
[ INFO ] Gathered candidate of Type: srflx Protocol: udp Address: 46.114.39.xx
[ WARN ] Could not connect using reflexive candidates, likely due to the network environment/configuration.
(it's fine on both devices when connected via wifi)
The original implementation allowed the client to select an arbitrary slot. The signalling server still supports that.
That worked by optimistically assuming we're side A, sending the PAKE A message and if the slot is already taken throw that away and start again as B.
The uncertainty about which side a client is made me nervous and I never needed to pick my slot anyway, so I disabled this. I'm leaving this issue open to remind myself that if needed this can be brought back.
I found ww -signal https://<wormhole.domain>.com send <file>
but is there also a way to set these config values in a config file so a specific server is always used?
we should be forgiving for accidental spaces around words etc.
Currently you have to go to the web UI and explicitly either create or join a wormhole. We can change that so:
We can also display a QR code to make it easier to send something from a phone.
Is this more confusing or less confusing than the current approach? Should we be worried about stale links with bad fragments laying around?
Guess what website has been pretty handy today as I try to send builds from my macbook to my desktop PC? :)
My flow is:
1 - on macbook load up webwormhole
2 - go to create wormhole
3 - note pasword
4 - go to desktop load up site and connect
5 - go back to laptop drag the files in
6 - go to PC and do things with them.
IF it was possible to cue up + drag files between stages 2->4 it'd save me a round trip to my laptop and back. Just a thought!
(I understand that that would require an interface change, though, and I don't have any constructive suggestions for how to do it other than maybe add a "queue up files" box that lets you queue up files beside the "new wormhole" button, or maybe beside the QR-Code?)
Very nice project, was wondering if possible to send sha256 file hash and only receive file if hashes between sender and receiver files don't match.
// add something like this to calc hash of file...
// import "crypto/sha256"
func hashsha256(filename string) ([]byte, error) {
h := sha256.New()
f, err := os.Open(filename)
if err != nil {
return h.Sum(nil), err
}
defer f.Close()
if _, err := io.Copy(h, f); err != nil {
return h.Sum(nil), err
}
return h.Sum(nil), err
}
in some rare cases where you need to quickly/easily get data out of systems where default gateway goes to a closed network.
I would be nice but not a priority to be able to specify the source IP address to choose to initiate the outgoing connection.
https://formin97.tistory.com/234
also perhaps I should file a separate detailed issue, it would also be useful to be able to traverse http proxies (I have setup the http_proxy environment variable for the cli but I can't get the browser and the cli to connect)
root@srv:/tmp# go get -u webwormhole.io/cmd/ww
cannot find package "github.com/pion/webrtc/v2" in any of:
/usr/lib/go-1.15/src/github.com/pion/webrtc/v2 (from $GOROOT)
/root/go/src/github.com/pion/webrtc/v2 (from $GOPATH)
root@srv:/tmp# dpkg -l |grep golang
ii golang 2:1.15-1longsleep1+bionic amd64 Go programming language compiler - metapackage
ii golang-1.15 1.15-1longsleep1+bionic all Go programming language compiler - metapackage
ii golang-1.15-doc 1.15-1longsleep1+bionic all Go programming language - documentation
ii golang-1.15-go 1.15-1longsleep1+bionic amd64 Go programming language compiler, linker, compiled stdlib
ii golang-1.15-src 1.15-1longsleep1+bionic amd64 Go programming language - source files
ii golang-doc 2:1.15-1longsleep1+bionic all Go programming language - documentation
ii golang-docker-credential-helpers 0.5.0-2 amd64 Use native stores to safeguard Docker credentials
ii golang-go 2:1.15-1longsleep1+bionic amd64 Go programming language compiler, linker, compiled stdlib
ii golang-src 2:1.15-1longsleep1+bionic amd64 Go programming language - source files
We should look into running a TURN server. It doesn't have to be fast, and it can be capped to whatever space capacity I have.
The fact that we don't ask browsers for media permissions means they only try WebRTC in "Mode 2". Plus, unless I misunderstood or am holding it wrong, Firefox and Chrome seem to exclude IPv6 from that mode, even if it's on the default route.
All this means we have slimmer chances of poking through NATs without relaying.
It's seems like the standard way to use short-lived auth token is this: https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
It also seems like everyone is using coturn.
I've tried with 3 different browsers on android 6 marshmallow. I tried with Chrome, link bubble, & Hermit. The button just says loading & The text box doesn't do anything when I type in a code.
The web version currently loads the whole file into a Blob, calls CreateObjectURL() on it, then triggers a download. To support sending large files we should probably use a ReadableStream instead of a Blob.
However, we can't CreateObjectURL() on a ReadableStream. Can we work around this somehow?
One thought: create an "echo" ServiceWorker that streams back whatever gets POSTed to it, which can then be downloaded. Maybe?
When pasting a URL with a new fragment to an existing browser window, state is not updated to take the new fragment into account. A reload is required to initiate the connection.
like magic-wormhole, bash completion for the command and some kind of js autocomplete for the web interface would be a major convenience.
tried both on ubuntu 18.04 and centos 7.5
steps tried:
git clone this repo
cd webwormhole
go get webwormhole.io/cmd/ww
output,
on centos
$ go get webwormhole.io/cmd/ww
go: downloading nhooyr.io/websocket v1.8.5
go: downloading golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
go: downloading rsc.io/qr v0.2.0
go: downloading github.com/NYTimes/gziphandler v1.1.1
go: downloading filippo.io/cpace v0.0.0-20200503185815-340c58da85ed
go: downloading github.com/pion/webrtc/v2 v2.2.11
go: downloading github.com/klauspost/compress v1.10.3
go: downloading github.com/gtank/ristretto255 v0.1.2
go: downloading golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
go: downloading golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
go: downloading github.com/pion/rtcp v1.2.1
go: downloading github.com/pion/logging v0.2.2
go: downloading github.com/pion/transport v0.10.0
go: downloading github.com/pion/quic v0.1.1
go: downloading github.com/pion/dtls/v2 v2.0.0
go: downloading github.com/pion/srtp v1.3.2
go: downloading github.com/pion/sdp/v2 v2.3.7
go: downloading github.com/pion/ice v0.7.15
go: downloading github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9
go: downloading github.com/pion/rtp v1.5.0
go: downloading github.com/pion/datachannel v1.4.17
go: downloading github.com/pion/sctp v1.7.6
go: downloading github.com/pkg/errors v0.9.1
go: downloading github.com/pion/mdns v0.0.4
go: downloading github.com/pion/stun v0.3.3
go: downloading golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
go: downloading github.com/pion/turn/v2 v2.0.3
go: downloading golang.org/x/text v0.3.0
go: downloading github.com/marten-seemann/qtls v0.2.3
go: downloading github.com/cheekybits/genny v1.0.0
$ find . -name ww
./cmd/ww
$ file ./cmd/ww
./cmd/ww: setgid directory
$ ls ./cmd/ww
file.go main.go pipe.go server.go
$ go run ./cmd/ww/main.go
# command-line-arguments
cmd/ww/main.go:22:13: undefined: send
cmd/ww/main.go:23:13: undefined: receive
cmd/ww/main.go:24:13: undefined: pipe
cmd/ww/main.go:25:13: undefined: server
on ubuntu:
#go get -u webwormhole.io/cmd/ww
go: downloading golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
go: downloading rsc.io/qr v0.2.0
go: downloading filippo.io/cpace v0.0.0-20200503185815-340c58da85ed
go: downloading github.com/NYTimes/gziphandler v1.1.1
go: downloading github.com/pion/webrtc/v2 v2.2.11
go: downloading nhooyr.io/websocket v1.8.5
go: downloading github.com/gtank/ristretto255 v0.1.2
go: downloading github.com/klauspost/compress v1.10.3
go: downloading github.com/pion/logging v0.2.2
go: downloading github.com/pion/rtp v1.5.0
go: downloading github.com/pion/ice v0.7.15
go: downloading github.com/pion/sdp/v2 v2.3.7
go: downloading github.com/pion/dtls/v2 v2.0.0
go: downloading github.com/pion/datachannel v1.4.17
go: downloading github.com/pion/srtp v1.3.2
go: downloading golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
go: downloading github.com/pion/rtcp v1.2.1
go: downloading github.com/pkg/errors v0.9.1
go: downloading github.com/pion/quic v0.1.1
go: downloading github.com/pion/transport v0.10.0
go: downloading github.com/pion/sctp v1.7.6
go: downloading github.com/pion/mdns v0.0.4
go: downloading github.com/pion/stun v0.3.3
go: downloading github.com/pion/turn/v2 v2.0.3
go: downloading github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9
go: downloading golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
go: downloading golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
go: downloading golang.org/x/text v0.3.0
go: downloading github.com/cheekybits/genny v1.0.0
go: downloading github.com/marten-seemann/qtls v0.2.3
go: github.com/pion/rtp upgrade => v1.6.0
go: golang.org/x/text upgrade => v0.3.3
go: github.com/pion/ice upgrade => v0.7.18
go: github.com/pion/transport upgrade => v0.10.1
go: golang.org/x/net upgrade => v0.0.0-20200822124328-c89045814202
go: github.com/pion/sdp/v2 upgrade => v2.4.0
go: github.com/marten-seemann/qtls upgrade => v0.10.0
go: github.com/pion/srtp upgrade => v1.5.1
go: nhooyr.io/websocket upgrade => v1.8.6
go: github.com/pion/rtcp upgrade => v1.2.3
go: golang.org/x/xerrors upgrade => v0.0.0-20200804184101-5ec99f83aff1
go: github.com/pion/sctp upgrade => v1.7.9
go: golang.org/x/crypto upgrade => v0.0.0-20200820211705-5c72a883971a
go: golang.org/x/sys upgrade => v0.0.0-20200824131525-c12d262b63d8
go: github.com/pion/datachannel upgrade => v1.4.20
go: github.com/pion/stun upgrade => v0.3.5
go: github.com/lucas-clemente/quic-go upgrade => v0.18.0
go: github.com/pkg/errors upgrade => v0.9.1
go: github.com/pion/turn/v2 upgrade => v2.0.4
go: github.com/klauspost/compress upgrade => v1.10.11
go: github.com/pion/webrtc/v2 upgrade => v2.2.24
go: github.com/pion/dtls/v2 upgrade => v2.0.2
go: github.com/pion/quic upgrade => v0.1.4
go: downloading golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
go: downloading github.com/pion/webrtc/v2 v2.2.24
go: downloading nhooyr.io/websocket v1.8.6
go: downloading github.com/klauspost/compress v1.10.11
go: downloading github.com/pion/dtls/v2 v2.0.2
go: downloading github.com/pion/quic v0.1.4
go: downloading github.com/pion/randutil v0.1.0
go: downloading github.com/pion/rtcp v1.2.3
go: downloading github.com/pion/sdp/v2 v2.4.0
go: downloading github.com/pion/srtp v1.5.1
go: downloading github.com/pion/ice v0.7.18
go: downloading github.com/pion/sctp v1.7.9
go: downloading github.com/pion/transport v0.10.1
go: downloading github.com/pion/rtp v1.6.0
go: downloading golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
go: downloading github.com/pion/datachannel v1.4.20
go: downloading github.com/pion/udp v0.1.0
go: downloading github.com/pion/turn/v2 v2.0.4
go: downloading golang.org/x/net v0.0.0-20200822124328-c89045814202
go: downloading github.com/lucas-clemente/quic-go v0.18.0
go: downloading github.com/google/uuid v1.1.1
go: downloading github.com/pion/stun v0.3.5
go: downloading google.golang.org/protobuf v1.23.0
go: downloading github.com/marten-seemann/qtls v0.10.0
go: downloading golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8
go: downloading github.com/golang/protobuf v1.4.2
go: downloading github.com/marten-seemann/qtls-go1-15 v0.1.0
go: downloading golang.org/x/text v0.3.3
go: github.com/golang/protobuf upgrade => v1.4.2
go: google.golang.org/protobuf upgrade => v1.25.0
go: github.com/pion/randutil upgrade => v0.1.0
go: downloading google.golang.org/protobuf v1.25.0
# go get -u webwormhole.io/cmd/ww
go: github.com/pkg/errors upgrade => v0.9.1
go: golang.org/x/text upgrade => v0.3.3
go: golang.org/x/sys upgrade => v0.0.0-20200824131525-c12d262b63d8
go: golang.org/x/crypto upgrade => v0.0.0-20200820211705-5c72a883971a
go: golang.org/x/net upgrade => v0.0.0-20200822124328-c89045814202
go: github.com/pion/randutil upgrade => v0.1.0
go: github.com/golang/protobuf upgrade => v1.4.2
go: github.com/pion/dtls/v2 upgrade => v2.0.2
go: google.golang.org/protobuf upgrade => v1.25.0
go: github.com/pion/transport upgrade => v0.10.1
go: github.com/pion/quic upgrade => v0.1.4
go: github.com/marten-seemann/qtls upgrade => v0.10.0
go: github.com/klauspost/compress upgrade => v1.10.11
go: github.com/lucas-clemente/quic-go upgrade => v0.18.0
go: golang.org/x/xerrors upgrade => v0.0.0-20200804184101-5ec99f83aff1
# go get -u webwormhole.io/cmd/ww
go: golang.org/x/text upgrade => v0.3.3
go: github.com/pion/randutil upgrade => v0.1.0
go: golang.org/x/sys upgrade => v0.0.0-20200824131525-c12d262b63d8
go: golang.org/x/crypto upgrade => v0.0.0-20200820211705-5c72a883971a
go: github.com/pkg/errors upgrade => v0.9.1
go: github.com/golang/protobuf upgrade => v1.4.2
go: github.com/pion/transport upgrade => v0.10.1
go: github.com/klauspost/compress upgrade => v1.10.11
go: golang.org/x/net upgrade => v0.0.0-20200822124328-c89045814202
go: golang.org/x/xerrors upgrade => v0.0.0-20200804184101-5ec99f83aff1
go: github.com/pion/quic upgrade => v0.1.4
go: github.com/pion/dtls/v2 upgrade => v2.0.2
go: github.com/lucas-clemente/quic-go upgrade => v0.18.0
go: google.golang.org/protobuf upgrade => v1.25.0
go: github.com/marten-seemann/qtls upgrade => v0.10.0
what is missing from command line client install instructions?!
both running go version go1.15 linux/amd64
When a connection fails there isn't any indication about why. It would be nice to have this to track down problems such as #26
Maybe adding some Console.log
messages would help, though doing that from the WASM seems like it might be a pain. @saljam, did you have plans to do any other sort of logging?
Tried pushing a file through the wormhole, and the file was listed at both ends - however, I could not click(touch) the file from an iPhone 8 in the web interface, so I couldn't download the file.
Please consider transferring the file timestamp from the source to the destination, just like the file name
This should target an iPhone SE screen size since that is the correct phone size.
This shouldn't be too hard since there are hardly any elements in the UI anyway.
The code in this repo can split into 3 categories, based on whether it's used by the Go client, the JS web interface, or both via wasm.
WASM:
JS-only:
Go-only:
It would make a lot of sense to reuse webwormhole.io/wordlist and webwormhole.io/wormhole from javascript. Part of this is avoiding code duplication, but more importantly I'm much more confident writing secure code in go than I am in js. I've had a lot of fun writing javascript targetting modern browsers but it's still scary as hell.
The main challenge and the reason this hasn't happened yes is that importing webwormhole.io/wormhole explodes the generated wasm filesize. I'll post some measurements later.
It's unclear to me how small is "small enough". I'm going to target 1MB and if someone has a better number with data to prove it please let me know!
Go 1.15 has a bunch of linker optimisations so we might see an improvement there but I doubt that's going to be all we need.
Thanks a lot, this project and the site is great! Here's a testimonial from my friend. If you want to use it, you have her permission.
the idea of a wormhole is exciting?
omg it's a real wormhole
this is beautiful I feel so connected
not even joking tbh
I've always wanted this
please stop the shaking
so from same machine i am trying to send file from cli and browser and cli maxes out on 300kbs and browsers goes to 4mbs recipient and sender is same in both cases any idea?
cli says relay by the way don't know what browser uses…
Transfer throughput is usually well below the expected line rate. This is mainly down to the implementation of SCTP used. This is known and is tracked by upstream SCTP stacks:
https://github.com/sctplab/usrsctp
Stack used in most browsers. Tracked in issue 245.
https://github.com/pion/sctp
Stack used by our command line client. Tracked in issue 62.
(Not linking to avoid polluting their issues.)
This is a pre-emptive meta-issue to track and measure the performance of SCTP stacks.
in fact any kind of feedback that the file you dropped is being sent would be great thanks
When I try to submit a file to the "filepicker" element using the Selenium 3.0.0 Powershell module on Windows 10 with Chrome 81.0.4044.113 I get the following error:
"Uncaught (in promise) NetworkError: Failed to execute 'send' on 'RTCDataChannel': Could not send data", source: https://webwormhole.io/main.js (59)
do you have any idea what would cause this error?
When trying to join a portal that's in the wrong state (stale/already used up link), the UI just hangs instead of offering to start a new portal.
Could the signalling server maybe send a clean error (instead of just dropping the websocket)? Or would it be fine to just go back to "disconnected" on a dropped websocket for any reason?
The current scheme ("slot number"-"dash-separated-words") is a straight clone of magic-wormhole's. It's pretty good and I like it, but I'm wondering if there are other variations we can explore.
One idea: PGP word list has parity information, so we could also encode the slot number as words with reversed parity. Where the parity changes we know it's the split between the slot and the secret.
Any other thoughts?
The webwormhole.io web interface appears to not work properly on recent versions of Chrom{e,ium}. Neither I nor the recipient of my file could get it to work on recent versions of the browser. It fails for me on chromium 85.0.4183.39 (current beta) and simply shows a "loading" button and will not accept a passphrase. The same happened on my recipient's end with Chrome 84.0.4147.89 (current stable).
Here's the error that's thrown in the console:
main.js:447 Uncaught (in promise) TypeError: chrome.runtime.getURL is not a function
at browserhacks (main.js:447)
at main.js:485
at main.js:520
The web interface works without a problem in Firefox (I tested on 72.0.2) and also from the command line (although the command line installation instructions do not work on either GNU/Linux or MacOS without exporting GO111MODULE=on
.
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.