Git Product home page Git Product logo

balena-sdk's Introduction

Balena SDK

The official JavaScript balena SDK.

npm version dependencies Build Status Build status

Role

The intention of this module is to provide developers a nice API to integrate their JavaScript applications with balena.

Installation

Install the balena SDK by running:

$ npm install --save balena-sdk

Platforms

We currently support NodeJS (18+) and the browser.

The following features are node-only:

  • OS image streaming download (balena.models.os.download),
  • balena settings client (balena.settings).

In Node you can simply require('balena-sdk'), but in the browser things are more complicated. The balena SDK provides a bundled single file for browsers, which allows you to include a single file with all dependencies included, available as balena-browser.min.js. This uses the UMD format, and will register itself as either a CommonJS or AMD module called balena-sdk if possible, or create a balenaSdk global if not. You can also use the es2018 version if desired.

Bundling for browsers

If you're using webpack, browserify, or a similar tool then you probably want to bundle the balena SDK into your application yourself, rather than using the pre-built balena-browser.min.js bundle. If you do that, you should be aware that you may pick up some dependencies that are actually unnecessary in the browser, because they're only used in Node environments. You can safely exclude these dependencies, if you're not using them yourself, and significantly reduce the size of your resulting bundle.

In the browser the balena SDK doesn't use the following dependencies:

  • fs
  • path
  • balena-settings-client
  • node-localstorage

For the future we're looking at ways to automatically exclude these in downstream bundles. See #254 for more information.

Bundling with pkg

The balena SDK includes builds for various ECMAScript versions that are dynamically selected at runtime (using @balena/es-version). For this reason, packagers like pkg are not able to automatically detect which assets to include in the output package. The following sample pkg section should be added to your application's package.json file to instruct pkg to bundle the required assets:

  "pkg": {
    "scripts": [
      "node_modules/balena-sdk/**/*.js"
    ],
    "assets": [
      "node_modules/pinejs-client-core/**/*"
    ]
  }

For more information, please refer to the respective documentation from the pkg project.

Documentation

The module exports a single factory function. Use it like this:

var balena = require('balena-sdk')({
	apiUrl: "https://api.balena-cloud.com/",
	dataDirectory: "/opt/local/balena"
})

Where the factory method accepts the following options:

  • apiUrl, string, optional, is the balena API url. Defaults to https://api.balena-cloud.com/,
  • builderUrl, string, optional , is the balena builder url. Defaults to https://builder.balena-cloud.com/,
  • deviceUrlsBase, string, optional, is the base balena device API url. Defaults to balena-devices.com,
  • dataDirectory, string or false, optional, ignored in the browser unless false, specifies the directory where the user settings are stored, normally retrieved like require('balena-settings-client').get('dataDirectory'). Providing false creates an isolated in-memory instance. Defaults to $HOME/.balena,
  • requestLimit, number.optional, the number of requests per requestLimitInterval that the SDK should respect.
  • requestLimitInterval, number.optional, - the timespan that the requestLimit should apply to in milliseconds. Defaults to 60000.
  • retryRateLimitedRequests, boolean.optional, when enabled the sdk will retry requests that are failing with a 429 Too Many Requests status code and that include a numeric Retry-After response header. Defaults to false.
  • isBrowser, boolean, optional, is the flag to tell if the module works in the browser. If not set will be computed based on the presence of the global window value,
  • debug, boolean, optional, when set will print some extra debug information.

See the JSDoc markdown documentation for the returned balena object in DOCUMENTATION.md.

Support

If you face any issues, please raise an issue on GitHub and the balena team will be happy to help.

Deprecation policy

The balena SDK uses semver versioning, with the concepts of major, minor and patch version releases.

The latest release of the previous major version of the balena SDK will remain compatible with the balenaCloud backend services for one year from the date when the next major version is released. For example, balena SDK v12.33.4, as the latest v12 release, would remain compatible with the balenaCloud backend for one year from the date when v13.0.0 is released.

At the end of this period, the older major version is considered deprecated and some of the functionality that depends on balenaCloud services may stop working at any time. Users are encouraged to regularly update the balena SDK to the latest version.

Tests

In order to run the balena SDK test suite, set the following environment variables from an account that exists and doesn't have a billing account code: WARNING: This will delete all applications and public keys of the test users. As such, all emails are required to contain the string +testsdk to avoid accidental deletion

  • TEST_EMAIL: The main account email.

  • TEST_PASSWORD: The main account password.

  • TEST_USERNAME: The main account username.

  • TEST_MEMBER_EMAIL: The email of the account for the membership tests.

  • TEST_MEMBER_PASSWORD: The password of the account for the membership tests.

  • TEST_MEMBER_USERNAME: The username of the account for the membership tests.

You also have to provide the following environment variables from an account that doesn't yet exist:

  • TEST_REGISTER_EMAIL: The email of the account to register.
  • TEST_REGISTER_PASSWORD: The password of the account to register.
  • TEST_REGISTER_USERNAME: The username of the account to register.

In order to test the billing methods for a paid account, you also have to configure the following environment variables:

  • TEST_PAID_EMAIL: The email of the paid account.
  • TEST_PAID_PASSWORD: The password of the account.

Note: The paid user's account billing code should be set to testdev so that it's tested against the test plan.

You can also, optionally, set the TEST_API_URL environment variable in order to run the tests using a different API backend (eg: https://api.balena-staging.com).

You can persist these settings by putting them all into a .env file in the root of this repo, in dotenv format (KEY=VALUE\n). This will be automatically detected and used in the tests. Make sure you don't accidentally commit this file (.env by default is gitignored, so hopefully this should be difficult).

Run the test suite by doing:

$ npm test

In order to make the develop & test cycle faster:

  • You can use mocha's .only & .skip variants to only run the subset of the test cases that is relevant to your changes/additions. You should make sure to remove those from your code before you push and make sure that the complete test suite completes successfully.
  • You can use npm run test:fast which fixes linting issues, only builds for a single ES target and runs the tests only on node. This can save time when implementing a method that interacts with an API endpoint, but should be avoided if the feature might work different or only in the browser.

Contribute

Before submitting a PR, please make sure that you

  • don't have uncommited changes on the documentation or the build output
  • don't have any .only or .skip in your tests
  • include typings for new methods
  • ran the lint script on the modified files
$ npm run lint:fix
  • include tests and that they pass
$ npm test

License

The project is licensed under the Apache 2.0 license.

balena-sdk's People

Contributors

acostach avatar aethernet avatar balena-ci avatar dependabot[bot] avatar dimitrisnl avatar emirotin avatar fisehara avatar flesler avatar flowzone-app[bot] avatar iamsolankiamit avatar josecoelho avatar joshbwlng avatar jsreds avatar jviotti avatar karaxuna avatar klutchell avatar lekkas avatar lucianbuzzo avatar mehalter avatar moranf avatar myarmolinsky avatar nitishagar avatar otaviojacobi avatar page- avatar pcarranzav avatar pimterry avatar sradevski avatar thgreasi avatar vipulgupta2048 avatar xginn8 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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

balena-sdk's Issues

onProgress has no state at the end of a download

The onProgress callback from resin.models.os.download doesn't return state when it reaches the end of the download.

Using the example resin.models.os.download code:

parameters =
        network: 'ethernet'
        appId: 91

resin.models.os.download parameters, '/opt/os.zip', (error) ->
        throw error if error?
    , (state) ->
        console.log "Total: #{state.total}"
        console.log "Received: #{state.received}"

gives the error:

Total: 107564591
Received: 106094176
Total: 107564591
Received: 106946144

/Users/hobochild/Documents/resin/autoTestPi/app.coffee:41
          console.log("Total: " + state.total);
                                       ^
TypeError: Cannot read property 'total' of undefined
  at Request.<anonymous> (/Users/hobochild/Documents/resin/autoTestPi/app.coffee:27:38)
  at Request.emit (events.js:117:20)
  at IncomingMessage.<anonymous> (/Users/hobochild/Documents/resin/autoTestPi/node_modules/resin-sdk/node_modules/request/request.js:1266:12)
  at IncomingMessage.emit (events.js:117:20)
  at _stream_readable.js:938:16
  at process._tickCallback (node.js:419:13)

Remove @option directives

These directives were added from codo, but are not supported by jsdoc. Find an alternative to describe them or remove.

Decide on the new device registration process

It (starting from resin-register-device@3) needs a provisioning key which will make it unusable for the ghost devices creation used by the CLI.

Decision to be made after we publish the browser-compat SDK.

NB: it's easy to prohibit this method in the browser, but
a) we need the provisioning keys for node.js tests
b) this method is used for inserting fixtures for other tests valid for the browser, like moving the device

Make the tests stable

See #236 (comment).

Currently the tests frequently (maybe 2/3rds of the time) fail due to ETIMEDOUT on Windows when making requests (of no consistent type) to the Resin API. The tests should reliably pass.

Error: "name" and "value" are required for setHeader().

Here is my REPL session:

> var resin = require('resin-sdk')
> var credentials = {'username': 'myusername', 'password': 'mypassword'}
> resin.auth.login(credentials) 
{ _bitField: 0,
  _fulfillmentHandler0: undefined,
  _rejectionHandler0: undefined,
  _progressHandler0: undefined,
  _promise0: undefined,
  _receiver0: undefined,
  _settledValue: undefined }
> Error: "name" and "value" are required for setHeader().
    at ClientRequest.OutgoingMessage.setHeader (_http_outgoing.js:333:11)
    at new ClientRequest (_http_client.js:101:14)
    at Object.exports.request (http.js:49:10)
    at Object.exports.request (https.js:136:15)
    at Request.start (/Users/benoit/git/figure/web/node_modules/request/request.js:904:30)
    at Request.end (/Users/benoit/git/figure/web/node_modules/request/request.js:1635:10)
    at end (/Users/benoit/git/figure/web/node_modules/request/request.js:676:14)
    at Immediate._onImmediate (/Users/benoit/git/figure/web/node_modules/request/request.js:690:7)
    at processImmediate [as _immediateCallback] (timers.js:358:17)

Need a check for a couple of system vars: USER and RESIN

From UI source system env vars are defined as:

isInvalidEnvVarName = do ->
        reservedNames = [ 'RESIN', 'USER' ]
        otherReservedNamesStart = 'RESIN_'
        errorMessage = "Environment variables #{reservedNames.join(', ')},
            and any beginning with '#{otherReservedNamesStart}' are reserved."

        return (name) ->
            return if not name
            return errorMessage if name in reservedNames
            return errorMessage if _.startsWith(name, otherReservedNamesStart)

Found this when fixing an issue with python sdk

Making the SDK suitable for the browser

As you know I've recently started investigating some lower-level modules and improving them to be browser-compatible and adding tests.

Now when I've reached the resin-pine module I've realized the problem is bigger than I assumed.
Both the CLI and the SDK are highly modular, but there's a global design issue that prevents the SDK from working in the browser as is.
The issue is that multiple low-level modules are actually not universal libraries because they keep knowledge about how things (mostly settings) are stored on the file system.

I've built a diagram to illustrate the modules relations: https://ns-pxnflxuoaj.now.sh/resin-sdk%20modules.html. Note that I've excluded many modules that are universal and are not part of the problem.

So in the ideal world, the SDK is supposed to be a universal library. Whatever is specific to the way the CLI works (like storing the apiUrl in the settings file) should only be known to the CLI itself. The SDK should accept all the information it needs as options.

Let's look at an example.
https://github.com/resin-io-modules/resin-pine is a nice library that provides all the sane params to the pinejs-client. But it has several flaws that prevent it from being browser-compatible. The main problem as mentioned above is that here https://github.com/resin-io-modules/resin-pine/blob/master/lib/pine.coffee#L57 it reads the apiUrl from the file system. In the browser there's even no analog of that. The URL is not stored in the localStorage. Instead, it's a constant in the code that should be used to instantiate the SDK. The CLI would still use the same settings library to read the URL (on its own) and then again pass it to the SDK.

There're a couple more issues of the similar nature:

  • here https://github.com/resin-io-modules/resin-pine/blob/master/lib/pine.coffee#L57 the library talks to a different library to check some data, but doesn't actually use it (it throws if the token is missing, but the actual token is not used). It's a small thing, but it's another example of the knowledge shared across the libraries. In my understanding, the library should just pass the options to the resin-request and let it complain (BTW some endpoints may not require auth).
  • here https://github.com/resin-io-modules/resin-pine/blob/master/lib/pine.coffee#L52 the library relies on the process.env which is Node-specific.
  • in the same line the RESIN_API_KEY variable is checked, but it's never used. It's confusing to find where it is actually read and passed to the resin-request which expects it as part of the options.

On the contrary, if we check the https://github.com/resin-io-modules/resin-register-device library we'll see a great example of universal code. It requires the user to pass it a PineJS instance and thus is completely agnostic of the way that instance is created and configured.


Given these examples here's how I see the plan to make the SDK universal:

  • resin-token will remain as it is (now that it can run in both node and the browser), and will be the only low-level library directly dealing with storage (because we want it to persist the token without our intervention).
  • as far as I can see resin-settings-client is currently serving two purposes: it keeps some useful defaults and shared info, and it also reads the user overrides from the file system. It has to be split somehow so that the first part can be used in the browser (if ever applicable).
  • resin-pine, resin-request will be made independent from resin-settings-client and process.env. Whatever they need should be passed to them as options. These are the modules I've found, but there can be more. This is a breaking change and will require the major versions to be released.
  • resin-sdk is made agnostic as well. It should receive the entire set of options, and pass them to sub-modules accordingly. Again it will require a new major version.
  • Finally, resin-cli should be responsible for reading he options (using resin-settings-client) and passing them to the SDK.

Then we'll be able to make the UI do the same but use its own means to get the properties (for example, the api key there will always be undefined, and the api URL is part of the UI code, etc.).

Browser version fails throws exception with webpack HMR.

I'm giving the new browser version a spin with a react and running into an exception when Hot module replacement is active.

Error in ./~/resin-sdk/build/resin.js
Module not found: 'resin-settings-client' in /Users/gaudi/work/resin-1984/node_modules/resin-sdk/build

 @ ./~/resin-sdk/build/resin.js 85:15-47

Still trying to debug, but if you have any ideas, send em my way.

@pimterry @emirotin

Make Resin-SDK builds gentler on the API

Currently if we're making lots of changes to the Resin-SDK, we end up flooding our API with traffic from ongoing builds: https://www.flowdock.com/app/rulemotion/resin-devops/threads/kyclI6am1xCvhpSjCOSv4HntTkQ.

It would be good to avoid this. As discussed elsewhere we currently duplicate a lot of testing work, which makes this far worse:

  1. Each change to a PR is built twice
  2. Each build is run on both Travis & AppVeyor
  3. Each of those builds runs a job for two Node versions
  4. Each job runs the test suite twice
  5. Each test suite runs the tests for Node and then the Browser

Some of this is useful, but 1 and 4 at least are not, and stopping those would reduce API load (and also build waiting times) by 75%.

In addition, the tests do many costly operations far more often than they appear in real world usage: creating new users, creating new applications. We could refactor the tests to do this less.

Missing/wrong factory argument errors aren't very helpful

In the new factory-method build, the errors you get if you miss an argument to the factory aren't very helpful.

  • No apiUrl argument:
    TypeError: Parameter "url" must be a string, not undefined
    Note that url != apiUrl - this error comes from Resin-Pine.
    Arguably this should just default to our prod API endpoint anyway?

  • No dataDirectory argument in Node:
    TypeError: Path must be a string. Received null (what?)

  • No imageMakerUrl argument:
    No error, but the docs says it's required - presumably something breaks later?
    Arguably this should just default to our prod API endpoint anyway?

  • No options argument provided at all:
    TypeError: Cannot read property 'isBrowser' of undefined
    We should default to an empty object to make things nice and easy for devs.

Fix NPM publishing

The last published version of the CLI lacks the following items:

  • The CHANGELOG version should link to a visual diff of all the commits between the version and the previous one. See the bottom of the CHANGELOG.md for examples.
  • The version in the CHANGELOG entry says v5.2.1, but the package.json was updated with v5.3.0.
  • The commit that was published to NPM should have an annotated tag.
  • The CHANGELOG entry date says April 24, but should be April 26.
  • The CHANGELOG entry is lacking other changes that were implemented in this new release, like all the other supervisor endpoints.

Stop bundling `crypto-browserify` in the resin-browser build

See #248 (comment) and #248 (comment).

This is included because we use randomstring in resin-register-device, which is cheap in Node, but expensive and maybe unnecessary in the browser. Dropping this should reduce the browser build size by up to 50% (depending on the size of the replacement).

This is related to #250, but different, since it's an artifact of the browserify process, rather than the modules we're directly using. Might be worth looking at them together anyway.

Switch from names to IDs

Right now the SDK operates on many resources by using human-readable things like app names and device uuids / names.

That makes sense for the CLI because they're way easier to type and recognize for the end users.
But doesn't make much sense to the programmatic usage. For example, the UI already operates on IDs and using the names means extra request round-trips.

We should make the SDK target the programmatic usage and accepts the IDs everywhere as the primary way to identify things.

The name -> ID conversion could live in a separate layer inside of the SDK, or can be moved to the CLI code (potentially as a compatibility wrapper around the SDK instead of patching all the existing method calls).

This should be handled as a separate major version after we merge the initial support for the browser.

Slug form is not a valid input "resin.models.application.create() "

It appears that the slug form doesn't work.
Output while running the example from the help section:

Unhandled rejection ResinInvalidDeviceType: Invalid device type: raspberry-pi

Meanwhile, if i replace the raspberry-pi in the code with Raspberry Pi it works as it should.

Integrate API Keys with SDK

you first start with your auth token and do a POST request to https://api.resin.io/application/#{application.id}/generate-api-key (sending the token in the Auth header) - see https://github.com/resin-io/resin-sdk/blob/397c0e5dc570c3d094561b6d3d27ae3254d9a141/build/models/application.js#L324 for example usage
the server should reply with an API key (json-encoded string, so you may need to strip an extra pair of quotes)

after that you add ?apikey=API_KEY to all your API requests and not send the token header anymore

Resin OS Download state

Extend resin.models.os.download() state to provide eta, remaining, etc.

Ideally, we can create a ProgressState class that provides an uniform interface to the state, and we can then make Resin CLI Visuals progress bar accept a ProgressState as an argument.

Update pine '$filter' expressions to latest format

The warning in several commands, like `resin devices --app <appName. indicates the correct format:

pinejs-client deprecated: `$filter: a: b: ...` is deprecated, please use `$filter: a: $any: { $alias: "x", $expr: x: b: ... }` instead.

Throw an error if calling function that requires authentication

Currently, if we call a function that requires authentication but we didn't login yet, no error is thrown and is hard to diagnose why the function doesn't do what is expected to do.

The problem seems to reside in auth.getToken(). This method delegates to data.get() which returns undefined, and no error if the key was not found.

Unhandled rejection when using "resin.auth.authenticate"

Stacktrace

Unhandled rejection ResinRequestError: Request error: Unauthorized
  at C:\Users\korakas\Desktop\test_folder\node_modules\resin-sdk\node_modules\resin-request\build\request.js:129:13
  at tryCatcher (C:\Users\korakas\Desktop\test_folder\node_modules\resin-sdk\node_modules\bluebird\js\main\util.js:26:23)
  at Promise._settlePromiseFromHandler (C:\Users\korakas\Desktop\test_folder\node_modules\resin-sdk\node_modules\bluebird\js\main\promise.js:501:31)
  at Promise._settlePromiseAt (C:\Users\korakas\Desktop\test_folder\node_modules\resin-sdk\node_modules\bluebird\js\main\promise.js:577:18)
  at Promise._settlePromises (C:\Users\korakas\Desktop\test_folder\node_modules\resin-sdk\node_modules\bluebird\js\main\promise.js:693:14)
  at Async._drainQueue (C:\Users\korakas\Desktop\test_folder\node_modules\resin-sdk\node_modules\bluebird\js\main\async.js:123:16)
  at Async._drainQueues (C:\Users\korakas\Desktop\test_folder\node_modules\resin-sdk\node_modules\bluebird\js\main\async.js:133:10)
  at Immediate.Async.drainQueues [as _onImmediate] (C:\Users\korakas\Desktop\test_folder\node_modules\resin-sdk\node_modules\bluebird\js\main\async.js:15:14)
  at processImmediate [as _immediateCallback] (timers.js:367:17)

Consider adding the convenience methods to support dependent apps / devices

It will likely be useful to add the methods like application.getDependent, device.getDependent, etc.
These will be thin wrappers around existing methods encoding the semantics of requestion application where application.application == X.
Should be handled after we figure out the nuances behind the dependent / edge devices and settle on the terminology.

Unauthorized request

> resin.auth.loginWithToken(myToken, function(err){console.log(err)})
null 
> resin.auth.isLoggedIn(function(err, isloggedIn){console.log(isloggedIn)})
true 
> resin.auth.whoami(function(err, me){console.log(me)})
gh_benoitguigal 
> resin.models.device.isOnline('resin_device_uuid', function(err, isOnline){console.log(err))}
{ [ResinRequestError: Request error: Unauthorized]
  body: 'Unauthorized',
  name: 'ResinRequestError',
  message: 'Request error: Unauthorized' }

I get the same error with any methods. It was working this morning so maybe there is some kind of throttling ?

Device status in the API

I was trying out the SDK, and running the demo resin.models.device.getAll() request. All my devices are offline at the moment and the result of the query for the device status are all like this:

status: 'Idle',
is_online: false,

Using the dashboard directly, these are the status possibilities:

  • Idle
  • Configuring
  • Updating
  • Offline
  • Post Provisioning

and all devices are marked as Status: Offline. Isn't that what the API should return too, instead of Idle? Or the meaning of the words are different for the API and the dashboard? Both are acceptable answers, of course, just checking if this is an API mismatch or a lack of documentation or something else.

Add SauceLabs or BrowserStack tests

We need to test this module (and ideally lower-level sub-modules) in the real browsers. PhantomJS is OK to check the compatibility with generic browser environment, but it's not the real browser.

I've recently hit a bug where Chrome had different implementation of some internal property.

Support all the PineJS options

Like extra filtering, ordering, selecting specific fields, eager-loading, etc.
This makes sense to all the methods that get multiple resources, like application.getAll.
I'd just allow passing the entire options as a single object and merging them properly (1 thing to be careful with is the $filter)

Fix flaky tests

Some of the tests intermittently fail due to race conditions, and this happens enough that between the 8 different test executions most PR builds fail overall. We should make these stable.

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.