GithubHelp home page GithubHelp logo

node-unifiapi's People

Contributors

delian avatar ianisms avatar krystophv avatar lzricardo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-unifiapi's Issues

UbiOS not supported / Incorrect device count

Hello,

Unfortunately, I have not been able to get this working yet. Here is what I have:

let CloudAPI = require('node-unifiapi/cloudapi');
let cloud = CloudAPI({
    //deviceId: '', // The cloud id of the device
    username: 'unifi',
    password: 'unifi',
    debug: true, // More debug of the API (uses the debug module)
    debugNet: true // Debug of the network requests
});


cloud.self()
    .then(() => cloud.devices())

**username and password excluded for obvious reasons

I receive:

{ response:
   { debugId: 3,
     headers:
      { date: 'Sat, 18 Apr 2020 10:04:08 GMT',
        'content-type': 'application/json; charset=utf-8',
        'content-length': '24',
        connection: 'close',
        'access-control-allow-origin': 'https://account.ubnt.com',
        vary: 'Origin',
        'access-control-allow-credentials': 'true' },
     statusCode: 200,
     body: '{"count":0,"devices":[]}' } }
  CloudAPI Received response: string +372ms
  CloudAPI now we have object +1ms

Which I know isn't correct. I looked on unifi.ui.com (The new UbiOS page) and this is in the requests:

devices

https://cloudaccess.svc.ui.com/devices?withUserData=true
https://ubic-stabilizer.svc.ui.com/api/airos/v1/unifi/devices?page=0&page_size=1000

devuser
devpage

So the second one (which I think your API is calling) is showing 0, whereas the first one is accurate.

It also looks like there is a 'list' which is at: https://cloudaccess.svc.ui.com/list, which also returns correct info.

unifi

Also: URL for getting the new UbiOS devices (UDMs) https://cloudaccess.svc.ui.com/devices?type=ucore,ulp&withUserData=true

Any help is much appreciated :)

2FA not supported

When logging in, the response has a required: ["2fa"]

I assume 2FA isn't supported yet?

WRTC not working on Azure

Azure App Service is not happy with the wrtc lib. Trying to use a different webrtc lib but not having any luck. Both easyrtc and webrtc.io fail in the same fashion.

My config:

import mywrtc from 'webrtc.io'; // import mywrtc from 'easyrtc';
...
this.unifiCloud = uCloud({
    deviceId: this.config.controllerDeviceId,
    username: this.config.username,
    password: this.config.password,
    webrtc: mywrtc,
    debug: app.logger.level === 'debug',
}).api;

And then after making a call using the api like so:

this.unifiCloud.authorize_guest(
        guest.macAddr,
        validMinutes
    ).then((data) => {
        resolve(data);
    }, (err) => {
        reject(err);
    });

I get the following error in the log (showing relevant part):

  WssAPI Connected wss://device-airos.svc.ubnt.com/api/airos/v1/unifi/events +392ms
  WssAPI Message Sending:  ping +0ms
  CloudAPI WebSocket is connected +2ms
  WssAPI Message Received: pong +82ms
  CloudAPI Received response: string +420ms
  CloudAPI now we have object +0ms
  CloudAPI WEBRTC_WS_SENDING +2ms
  WRTCRequest WRTC_PEER_OPEN { iceServers:
   [ { urls: 'stun:global.stun.twilio.com:3478?transport=udp',
       url: undefined },
     { urls: 'turn:global.turn.twilio.com:3478?transport=udp',
       url: undefined,
       username: 'UN',
       credential: 'PW' } ] } { optional: [ { DtlsSrtpKeyAgreement: true }, { RtpDataChannels: true } ] } +0ms
  CloudAPI Error in opening webrtc +1ms
(node:22168) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot create property 'message' on string 'Could not authorize'
(node:22168) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
  WssAPI Message Sending:  ping +9s
  WssAPI Message Received: pong +81ms

Any ideas?

Login problem to the unifi api - voucher creation example ?

Hello,

I'm looking for example and explanation how to use :

https://github.com/delian/node-unifiapi

I have some AC-Pro aps, and cloudkey G2. i have configured the key, included the aps, and turned on the portal (for voucher auth)

Now in my web app i need to create vouchers using cloud api. That my app running on my server (that is on remote network, could generate the voucher, and this voucher would be available on the remote network where cloudkey g2 is.

I have connected the cloudkey to the ubnt cloud account and i can manage it , add voucher and everything works.

The problem is with using that nodejs api.

let cloud = require('node-unifiapi/cloudapi');
let r = cloud({
deviceId: '801bb78e12c80000000001a22aea000000000203c905000000066660aaaa', // The cloud id of the device
username: 'clouduser',
password: 'cloudpass',
// debug: true, // More debug of the API (uses the debug module)
// debugNet: true // Debug of the network requests
});
r.api.stat_sessions()
.then((data) => {
console.log('Stat sessions', data);
return r.api.stat_allusers();
})
.then((data) => {
console.log('AP data', data);
})
.catch((err) => {
console.log('Error', err);
})
questions:

do I have to use the device ID from the website url ? or is it possible just to use macadress ?
username is username that i log in to my ubnt account ?
as above the ubnt password ?
site is it shortsite name or the name shown in the website ?
If someone has got working code example that connects to account, runst auth, and calls the api for voucher generation, than i would be very happy to have a look on that, i do not have much expierience, and working example would be perfect to see.

Running on an Issue with basic example

I'm running the default example trying to connect to my unifi controller. Why am I getting this error?
Node version is v14.16.1, node-unifiapi: "^0.0.54"

UnifiAPI Debug is enabled +0ms
UnifiRequest UnifiAPI-request Initialized with options { username: 'mdandrea87', password: '(mypassword)', baseUrl: 'https://127.0.0.1:8443', debug: true, debugNet: false, gzip: true, site: 'default' } +2ms
UnifiAPI UnifiAPI Initialized with options { baseUrl: 'https://127.0.0.1:8443', username: 'mdandrea87', password: '(mypassword)', debug: true } +3ms
UnifiRequest Trying to log in with username: mdandrea87 and password: (mypassword) +1ms
UnifiRequest Successfuly logged in { rc: 'ok' } +1s
Error { meta: { rc: 'error', msg: 'api.err.NotFound' }, data: [] }

version without WebRTC?

@delian As the maintainer of the API client class this was derived from, I'm glad to see an API client for Node.js being developed and maintained!
For a specific project, I plan to use the node-unifiapi package but I won't be needing the WebRTC stuff. Is there a version without the WebRTC support or will it be stable enough when not using WebRTC?

Issues with example

Running your example code for the cloudapi I get these errors right after starting the node app.

ALSA lib control.c:953:(snd_ctl_open_noupdate) Invalid CTL
ALSA lib control.c:953:(snd_ctl_open_noupdate) Invalid CTL
ALSA lib control.c:953:(snd_ctl_open_noupdate) Invalid CTL
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM
ALSA lib control.c:953:(snd_ctl_open_noupdate) Invalid CTL
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM
ALSA lib control.c:953:(snd_ctl_open_noupdate) Invalid CTL
ALSA lib control.c:953:(snd_ctl_open_noupdate) Invalid CTL
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM
ALSA lib control.c:953:(snd_ctl_open_noupdate) Invalid CTL
ALSA lib control.c:953:(snd_ctl_open_noupdate) Invalid CTL
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM

Then if I let it sit I get this after about a minute followed by the app crashing.

Error UnifiAPI {
debugging: [Function],
netsite: [Function],
login: [Function],
logout: [Function],
authorize_guest: [Function],
unauthorize_guest: [Function],
kick_sta: [Function],
terminate_guest: [Function],
block_sta: [Function],
unblock_sta: [Function],
set_sta_note: [Function],
set_sta_name: [Function],
stat_sessions: [Function],
stat_daily_site: [Function],
stat_hourly_site: [Function],
stat_hourly_ap: [Function],
stat_sta_sessions_latest: [Function],
stat_auths: [Function],
stat_allusers: [Function],
list_guests: [Function],
list_guests2: [Function],
list_clients: [Function],
stat_client: [Function],
list_usergroup: [Function],
set_usergroup: [Function],
list_health: [Function],
list_dashboard: [Function],
list_users: [Function],
list_aps: [Function],
list_rogueaps: [Function],
list_sites: [Function],
stat_sites: [Function],
add_site: [Function],
remove_site: [Function],
list_wlan_groups: [Function],
stat_sysinfo: [Function],
list_self: [Function],
list_networkconf: [Function],
stat_voucher: [Function],
stat_payment: [Function],
create_hotspot: [Function],
list_hotspot: [Function],
create_voucher: [Function],
revoke_voucher: [Function],
list_portforwarding: [Function],
list_dynamicdns: [Function],
list_portconf: [Function],
list_extension: [Function],
list_settings: [Function],
restart_ap: [Function],
disable_ap: [Function],
enable_ap: [Function],
set_locate_ap: [Function],
unset_locate_ap: [Function],
site_ledson: [Function],
site_ledsoff: [Function],
set_ap_radiosettings: [Function],
get_settings: [Function],
get_settings_by_key: [Function],
set_settings: [Function],
set_guest_access: [Function],
set_guestlogin_settings: [Function],
rename_ap: [Function],
set_wlansettings: [Function],
list_events: [Function],
list_wlanconf: [Function],
get_wlanconf: [Function],
list_alarms: [Function],
set_ap_led: [Function],
set_ap_name: [Function],
set_ap_wireless: [Function],
status: [Function],
set_ap_network: [Function],
request_spectrumscan: [Function],
set_site_descr: [Function],
set_site_settings: [Function],
add_hotspot2: [Function],
list_hotspot2: [Function],
delete_hotspot2: [Function],
set_hotspot2: [Function],
remove_wlanconf: [Function],
add_wlanconf: [Function],
sdn_register: [Function],
sdn_unregister: [Function],
sdn_stat: [Function],
sdn_onoff: [Function],
extend_voucher: [Function],
buildSSHSession: [Function],
getSDPOffer: [Function],
sshSDPAnswer: [Function],
closeSSHSession: [Function],
connectSSH: [Function],
getSshTurnServers: [Function],
getTurnCredentials: [Function],
username: 'unifi',
password: 'unifi',
baseUrl: '',
debug: false,
debugNet: false,
gzip: true,
site: 'default',
net:
{ login: [Function: login],
logout: [Function: logout],
req: [Function: req] } }
Segmentation fault

Voucher creation - voucher for desired start and end active date ?

Hello, i need a voucher that will grant access to the network but can be used and validated as active only for specific time with start date and expire date.

Example:

I need to create voucher that will give the access for the user form 9 july 2019 11:23AM to 12 july 2019 12:00 PM, and before and after that date it will be not active but will be available in logs (not deleted) as history of access rights.

Any idea how to do that ?

We can generate the voucher for desired amount of time in hours, and than when the end period of access for that is reached we can revoke the voucher. But this does not block the voucher usage before starting date, and it will shorten the time of validity if usage started early, and needs online connection always for revoking (deleting the voucher that should be deactivated if not used).
SO it needs external script to work 24/7 just to add time validation to the voucher validity period control.
If there was a way to generate a voucher that will expire without a need of revoking it it would be good for offline voucher management

So any suggestions would be nice to hear, I will pay in beer or vodka (or whiskey) for the solution :D

Promise in creating the vouchers..

Hi ! Good Day
I'm using unifi 5.7.2
node 6.10.0

My issue is i can't get the voucher code after using the create_voucher function..
it only returns the created time..
how to get the code ?

Still dependent on wrtc

Looks like there are still parts that are looking for wrtc.

I just installed this library and tried to run the basic example in the docs. Using the latest version off of NPM (0.0.50) with Node v8.9.3.

Getting an Error: Cannot find module 'wrtc'.

This is the code I'm trying to run.

let unifi = require('node-unifiapi');
let r = unifi({
	baseUrl: url, // The URL of the Unifi Controller
	username: username
	password: password,
	site: site
	// debug: true, // More debug of the API (uses the debug module)
	// debugNet: true // Debug of the network requests (uses request module)
});
r.stat_sessions()
.then((data) => {
        console.log('Stat sessions', data);
        return r.stat_allusers();
})
.then((data) => {
        console.log('AP data', data);
})
.catch((err) => {
        console.log('Error', err);
})

I'd prefer to try and avoid web-rtc since I'm not planning to use any of those capabilities. Not sure why it's triggering the wrtc error.

Not Working on RPI

I am trying to use this on a raspberry pi so that i can 'with the push of a button' create a voucher - this is solely experimental at the minute. Is there any known reason to why this wouldn't work or cant work?
i have attached a the snippet error i get when trying to run this.

/home/pi/node_modules/node-unifiapi/index.js:1
(function (exports, require, module, __filename, __dirname) { let debug = require('debug')('UnifiAPI');
                                                                                          ^

TypeError: require(...) is not a function
    at Object.<anonymous> (/home/pi/node_modules/node-unifiapi/index.js:1:91)
    at Module._compile (module.js:653:30)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
    at Function.Module._load (module.js:498:3)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/home/pi/nodetest/buttonpress.js:1:75)
    at Module._compile (module.js:653:30)

Authentication Error

I have a Unifi Controller installation that I have not previously configured (just installed on an Ubuntu server). When attempting to login with unifi.login('ubnt', 'ubnt'), I'm getting the following error:

Error "Authentication error [object Object]"

Should I be able to log in using this API before I've configured the location using the web UI? If so, is there any way I can get more detailed information about the error?

Note: Once I set up a username/pw through the Web UI, I am able to login successfully.

UnifiAPI.stat_session function not allowing single mac filtering

Comparing the stat_session function (show login sessions for clients), with the PHP version of the Unifi API class, I noticed that the PHP version lets you perform a search of sessions between a data range for a specific MAC. The NodeJS version doesn't accept a MAC as a parameter.

Node implementation

* List client sessions

PHP Implementation

https://github.com/Art-of-WiFi/UniFi-API-client/blob/master/src/Client.php#L669

An example of using this function call using the netsite function in NodeJS:

const moment = require('moment');

let conf = {
    mac: "<mac address>",
    'type': 'all',
    'start': moment().subtract(7, 'days').unix(),
    'end': moment().unix()
};

r.netsite('/stat/session', conf, {}, undefined, <sitecode>)
.then((data) => {
   console.log(data.data);
}).catch(err => console.log('Error',err));

Again will be issuing a pull-request soon, but documenting this here.

Merge with node-unifi?

You may have noticed it, but I there is an alternative NodeJS package for working with a UniFi Controller using NodeJS. It is called ‚node-unifi‘ (https://github.com/jens-maus/node-unifi) and I am working on it since several years.

And since I recently updated it and found your alternative node module I wonder if there are chances we get our efforts merged together?!? What do you think about getting our code merged and probably working on an advanced node-unifi v2 with introduced Promise support which can then be a combination of our two modules.

Opinions?

API call for Total Bytes used per Voucher?

I've been going through the API calls and I can't find one where I specify the voucher code, and it gives me the total used per that voucher.

I'm asking because, users of my hotspot are always asking for how much data they've used. I wanna put together a simple Express app where they could enter their voucher, and have the amount used and amount left shown.

I'm loving this project of yours so much. Thanks.

Segmentation fault (core dumped)

When I run the example for the Cloud Access with my credentials I get this error:
Segmentation fault (core dumped)
When I change the credentials to be incorrect I just get a long JSON error.
My code is exactly the same as the code in the example but here it is just in case:

let r = cloud({
    deviceId: '892se6-734c-461c-9ca8-e80bgdf5c226', // The cloud id of the device
    username: 'TylerLafayette',
    password: '<passwordHere>',
    // debug: true, // More debug of the API (uses the debug module)
    // debugNet: true // Debug of the network requests
});
r.api.stat_sessions()
    .then((data) => {
        console.log('Stat sessions', data);
        return r.api.stat_allusers();
    })
    .then((data) => {
        console.log('AP data', data);
    })
    .catch((err) => {
        console.log('matError', err);
    })```

Bug in implementation of UnifiAPI.stat_sta_sessions_latest

So looks like there're some typos in this function, here:

UnifiAPI.prototype.stat_sta_sessions_latest = function(mac = '', limit = 5, sort = '-asoc-time', site = undefined) {

The current code looks like so:

UnifiAPI.prototype.stat_sta_sessions_latest = function(mac = '', limit = 5, sort = '-asoc-time', site = undefined) {   
    return this.netsite('/stat/sessions', {   
        mac: mac.toLowerCase(),   
        '_limit': limit,   
        '_sort': sort   
    }, {}, undefined, site);   
};

This returns an empty dataset, for 2 reasons:

  1. The API to call is going to /stat/sessions not /stat/session.
  2. The sorting parameter should be -assoc_time not -asoc-time

Putting this here right now as I'm still working through other calls to try and debug if there are any issues. If I aggregate a few more I'll create a pull request to consolidate these changes.

Error { data: [], meta: { msg: 'api.err.LoginRequired', rc: 'error' } }

After the server keeps running for a while, making a request returns the error:

Error { data: [],
  meta: { msg: 'api.err.LoginRequired', rc: 'error' } }

A simple workaround I'm thinking of is logging in per each transaction.

That's the right way? Or there's a setting somewhere I'm missing?

cloud access problems with 5.9 controller version

Hi Delian, first of all, thank you for your effort to create the scripts related unifi. Unfortunately Ubiquiti looks lazy to do that.

I can able to connect and send API commands via the cloud to my controllers which version is 5.6.22 but I have other two controllers with 5.9.29 version number and they don't work anymore. After some sniffing, it looks Ubiquiti was changed login URLs.

https://device-airos.svc.ubnt.com/api/airos/v1/unifi/turn/creds?username=xxx

That url doesn't show on my traffic logs when I accessing to 5.9.x controller from the web browser. Instead of I see this URL to authorize with OAuth mechanism.

https://sso.ubnt.com/oauth2/authorize?client_id=xx

To clarify the problem I added the debug log from your script. As I said, 5.6.x controllers in the same account and works properly with the same script.

Thanks in advance !

WssAPI Connected wss://device-airos.svc.ubnt.com/api/airos/v1/unifi/events +317ms
WssAPI Message Sending: ping +0ms
CloudAPI WebSocket is connected +2ms
{ request:
{ debugId: 2,
uri:
'https://device-airos.svc.ubnt.com/api/airos/v1/unifi/turn/creds?username=X',
method: 'GET',
headers:
{ 'Content-type': 'application/json',
Referer:
'https://account.ubnt.com/login?redirect=https%3A%2F%2Funifi.ubnt.com',
Origin: 'https://account.ubnt.com',
dnt: 1,
host: 'device-airos.svc.ubnt.com',
cookie:
'lithiumRest:ubnt=x; lithiumSSO:ubnt=x; NODEBB_AUTH=x; UBIC_AUTH=x' } } }
WssAPI Message Received: pong +62ms
{ response:
{ debugId: 2,
headers:
{ date: 'Thu, 27 Dec 2018 10:12:42 GMT',
'content-type': 'text/plain; charset=utf-8',
'content-length': '0',
connection: 'close',
'access-control-allow-origin': 'https://account.ubnt.com',
vary: 'Origin',
'access-control-allow-credentials': 'true',
server: 'Cowboy',
'x-folgian-request-id': 'x',
'set-cookie': [Array] },
statusCode: 403,
body: '' } }
CloudAPI Error in opening webrtc +215ms

SyntaxError using the CloudAPI/wrtc

Non-cloud version is working well, but I wanted to ssh into a device.

Tried to run the list_aps() and got the following error.

SyntaxError: Unexpected end of JSON input
    at Object.parse (native)
    at registerQ (/home/webdev/repos/ubiquity-toolkit/node_modules/node-unifiapi/lib/webrtc-request.js:417:63)
    at _q.(anonymous function).forEach.n (/home/webdev/repos/ubiquity-toolkit/node_modules/node-unifiapi/lib/webrtc-request.js:45:49)
    at Array.forEach (native)
    at WRTC.fireQ (/home/webdev/repos/ubiquity-toolkit/node_modules/node-unifiapi/lib/webrtc-request.js:45:36)
    at RTCDataChannel.channel.(anonymous function) [as onmessage] (/home/webdev/repos/ubiquity-toolkit/node_modules/node-unifiapi/lib/webrtc-request.js:255:26)
    at /home/webdev/repos/ubiquity-toolkit/node_modules/wrtc/lib/eventtarget.js:32:26
    at _combinedTickCallback (internal/process/next_tick.js:67:7)
    at process._tickCallback (internal/process/next_tick.js:98:9)

Segfault 139 on Linux

Attempting to run this lib on a linux machine results in a SegFault 139:

`Segmentation fault (core dumped)

npm ERR! Linux 4.4.0-83-generic
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "start"
npm ERR! node v6.9.5
npm ERR! npm v3.10.10
npm ERR! code ELIFECYCLE
npm ERR! project start: node server.js
npm ERR! Exit status 139
npm ERR!

Maybe typo in unauthorise_guest method

UnifiAPI.prototype.unauthorize_guest = function(mac = '', site = undefined) {
return this.netsite('/cmd/stamgr', {
        cmd: 'uauthorize-guest',//in this line
        mac: mac.toLowerCase()
    }, {}, undefined, site);
};

Simple Example not working

Using the following

let UnifiAPI = require('node-unifiapi'); let unifi = UnifiAPI({ baseUrl: 'https://127.0.0.1:8443', // The URL of the Unifi Controller username: 'ubnt', password: 'ubnt', debug: true, // More debug of the API (uses the debug module) // debugNet: true // Debug of the network requests (uses request module) }); unifi.login(username, password) .then(data => console.log('success', data)) .catch(err => console.log('Error', err))

I get the following output

root@gateway:~/unifi_tools# node --use-strict ./ap.js
/root/node_modules/node-unifiapi/index.js:68
UnifiAPI.prototype.netsite = function(url = '', jsonParams = undefined, headers = {}, method = undefined, site = undefined) {
^

SyntaxError: Unexpected token =
at exports.runInThisContext (vm.js:53:16)
at Module._compile (module.js:373:25)
at Object.Module._extensions..js (module.js:416:10)
at Module.load (module.js:343:32)
at Function.Module._load (module.js:300:12)
at Module.require (module.js:353:17)
at require (internal/module.js:12:17)
at Object. (/root/unifi_tools/ap.js:1:78)
at Module._compile (module.js:409:26)
at Object.Module._extensions..js (module.js:416:10)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.