Giter VIP home page Giter VIP logo

fs-js-lite's Introduction

npm Build Status Coverage Status

FamilySearch Lite JavaScript SDK

Lite JavaScript SDK for the FamilySearch API. This SDK is designed for use in a web browser and uses XMLHttpRequest.

There is also an API Explorer which is built using the SDK and a Node.js sample app.

Install

Download or include the SDK directly from the CDN

<script src="https://unpkg.com/fs-js-lite@latest/dist/FamilySearch.min.js"></script>

Or install from npm

npm install --save fs-js-lite

Usage

The SDK includes a UMD wrapper to support being loaded in AMD environments (RequireJS) and Node as well as being loaded as a browser global.

fs.get('/platform/users/current', function(error, response){
  if(error){
    console.error(error);
  } else {
    console.log(response.data);
  }
});

Initialization Options

// Create a client instance. All available options are shown here for the sake
// of documentation though you normally won't specify all of them.
var fs = new FamilySearch({
  
  // Specify the FamilySearch reference environment that will be used. Options 
  // are: 'production', 'beta', and 'integration'. Defaults to 'integration'.
  environment: 'production',
  
  // App keys are obtained by registering you app in the FamilySearch developer's center.
  // https://familysearch.org/developers/docs/guides/gs1-register-app
  appKey: 'ahfud9Adjfia',
  
  // Required when using OAuth.
  // https://familysearch.org/developers/docs/guides/authentication
  redirectUri: 'https://example.com/fs-redirect',
  
  // Optionally initialize the client with an access token. This is useful when
  // authentication is handled server-side.
  accessToken: 'myaccesstoken',
  
  // Save the access token in a cookie and load if from a cookie so that the
  // session isn't lost when the page reloads or changes. Defaults to false.
  // Use the `tokenCookie` option to change the name of the cookie.
  saveAccessToken: true,
  
  // Name of the cookie where the access token will be stored when `saveAccessToken`
  // is set to `true`. Defaults to 'FS_AUTH_TOKEN'.
  tokenCookie: 'FS_AUTH_TOKEN',
  
  // Path value of the access token cookie.
  // Defaults to the current path (which is probably not what you want).
  tokenCookiePath: '/',
  
  // Maximum number of times that a throttled request will be retried. Defaults to 10.
  maxThrottledRetries: 10,
  
  // List of pending modifications that should be activated.
  pendingModifications: ['consolidate-redundant-resources', 'another-pending-mod'],
  
  // Optional settings that enforces a minimum time in milliseconds (ms) between
  // requests. This is useful for smoothing out bursts of requests and being nice
  // to the API servers. When this parameter isn't set (which is the default)
  // then all requests are immediately sent.
  requestInterval: 1000
  
});

You can also change these options later via config(). It accepts the same options.

fs.config({
  appKey: 'mynewappkey'
})

Authentication

We recommend reading the FamilySearch Authentication Guide before deciding which authentication methods are best for you.

oauthRedirectURL([state]) - Obtain the URL of the login screen on familysearch.org that the user should be redirected to for initiating authentication via OAuth 2. This method will automatically assemble the URL with the proper query parameters (the app key and redirect URI that were specified when the sdk client was created).

oauthRedirect([state]) - Begin OAuth 2 by automatically redirecting the user to the login screen on familysearch.org. This only works in the browser as a shortcut for window.location.href = fs.oauthRedirectURL();.

oauthToken(code, callback) - In the second step of OAuth 2, exchange the code for an access token. The access token will be saved if that behavior is enabled. The callback is a normal request callback that recieves error and response parameters.

oauthUnauthenticatedToken(ipAddress, callback) - Request an unauthenticated access token. The access token will be saved if that behavior is enabled. ipAddress is the IP address of the user. The callback is a normal request callback that recieves error and response parameters.

oauthResponse([state,] callback) - When handling the OAuth 2 response in the browser, call this method which is automatically extract the code from the query parameter and call oauthToken() for you. The method will return false if no code was found in the query paremeter or when the optional state parameter is given and it doesn't match the state paremeter in the query. true is returned when a code was found and a request was sent to exchange the code for an access token. In that case you still must use a callback to check the response of that request and verify whether an access token was received.

oauthPassword(username, password, callback) - Use the OAuth password flow. Access tokens will be automatically saved in a cookie if that behavior is enabled. The OAuth password flow is disabled by default for app keys. Contact Developer Support to inquire about it being enabled for your app key. Typically only mobile and desktop apps are granted permission.

setAccessToken(accessToken) - Set the access token. This will also save it in a cookie if that behavior is enabled.

getAccessToken() - Get the access token if one is set. This does not send a request to the API to initiate authentication, it just returns what is currently stored in the sdk client's properties.

deleteAccessToken() - Delete the access token. This doesn't actually invalidate the access token it just removes it from the sdk client.

Authentication in the Browser

Authentication can be completely handled in the browser. First you would call oauthRedirect() to send the user to the login screen on familysearch.org. Then when the user returns to your app you would call oauthResponse() to complete authentication. You would also likely want to set the saveAccessToken to true when instantiating the SDK.

Authentication in Node.js

When handling authentication on the server, you first redirect the user to the URL returned by oauthRedirectURL(). Then when the user returns to your app you will retrieve the code from the query paremeters and call oauthToken() to complete authentication. When authentication is finished you would typically save the access token in a session so that the user remains authenticated between page loads. See the node sample app for an example of how this can be done with Express.

You can also use a mixed approach to authentication by beginning in the browser with the redirect to familysearch.org and handling the response on the server.

Requests

// GET
fs.get('/platform/users/current', function(error, response){ });

// POST
fs.post('/platform/tree/persons', {
  body: { persons: [ personData ] }
}, function(error, response){ });

// HEAD
fs.head('/platform/tree/persons/PPPP-PPP', function(error, response){ });

// DELETE
fs.delete('/platform/tree/persons/PPPP-PPP', function(error, response){ });

// The SDK defaults the Accept and Content-Type headers to application/x-fs-v1+json
// for all /platform/ URLs. But that doesn't work for some endpoints which use
// the atom data format so you'll need to set the headers yourself.
fs.get('/platform/tree/persons/PPPP-PPP/matches?collection=records', {
  headers: {
    Accept: 'application/x-gedcomx-atom+json'
  }
}, function(error, response){ });

// Underneath the covers, `get()`, `post()`, `head()`, and `delete()` call the
// `request()` method which has the same method signature.
fs.request('/platform/tree/persons/PPPP-PPP', {
  method: 'POST',
  body: { persons: [ personData ] }
}, function(error, response){ });

// The options object is optional. When options are not include, the SDK will
// automatically detect that the callback is the second parameter.
// The `method` defaults to 'GET'.
fs.request('/platform/tree/persons/PPPP-PPP', function(error, response){ });

Request options:

  • method - The HTTP method. Supported methods are GET, POST, HEAD, and DELETE. Defaults to GET.
  • headers - HTTP request headers in an object where header names are keys. The SDK will default Accept and Content-Type to application/x-fs-v1+json. Usually that's what you want but some endpoints require application/x-gedcomx-atom+json so you'll have to specifically set that.
  • body - The request body. Only valid when the method is POST. The body may be a string or an object.

Any other options you include in the request will be made available to middleware at request.options. This allows you to pass request options to custom middleware.

Responses

Responses are objects with the following properties and methods:

  • statusCode - Integer
  • statusText - String
  • headers - Map of the response headers. Header names are lowercased.
  • body - Response body text, if it exists
  • data - Object; only exists if the body is parsable JSON
  • originalUrl - String
  • effectiveUrl - Will be different from originalUrl when the request is redirected
  • requestMethod - HTTP method used on the request
  • requestHeaders - HTTP headers set on the request
  • redirected - Boolean specifying whether the request was redirected
  • throttled - Boolean specifying whether the request was throttled
  • retries - Integer. Number of times the request was retried. Requests are only retried when they are throttled.

Error Handling

There are two types of errors: network errors and HTTP errors.

For HTTP errors the developer needs access to the response object. The SDK makes no attempt to interpret HTTP status codes and enable built-in error handling behaviors. It is the developer's job to interpret and respond to HTTP errors.

Network errors are returned as the first argument to response callbacks.

fs.get('/platform/tree/persons/PPPP-PPP', function(error, response){
  
  if(error){
    alert('Network error');
  } 

  else if(response.statusCode >= 500){
    alert('Server error');
  }
  
  else if(response.statusCode >= 400){
    alert('Bad request');
  }
  
  else {
    alert('Looking good');
  }
    
});

Redirects

Redirects are not automatically followed by the SDK. Usually you'll want to automatically follow the redirects but in some cases such as fetching portraits you just want to know what the redirect URL is but not actually follow it. Thus you must specify via a request option when you want the SDK to follow the redirect.

client.get('/platform/tree/current-person', {
  followRedirect: true
});

Throttling

The SDK will automatically retry throttled requests and obey the throttling headers which tell how long to wait until retrying the request. Response objects include the retries property which is an integer specifying how many times the request was throttled and a throttled property which is true when the request has been throttled.

Middleware

The SDK allows for customizing the request and response processing via middleware. Middleware can be used to support caching, logging, and other features.

Request Middleware

// Add request middleware to log all requests
fs.addRequestMiddlware(function(client, request, next){
  console.log(request.method + ' ' + request.url);
  next();
});

Request middleware is applied to every request the API makes. Request middleware is a function with the signature (client, request, next).

  • client is the instance of the FamilySearch sdk that the request is associated with.
  • request is an object that has {url, method, headers, body}.
  • next is a method that must be called when the middleware is done. Its signature is function(error, response). In most cases nothing will be returned. When an error is returned the middleware chain will be canceled and the error will be returned to the request callback. A response may be returned by the middleware to enable caching. In this case the response is immediately returned.

Request middleware is applied in the order that it was added. The SDK sets up some request middleware by default.

Response Middleware

// Add response middleware to log all responses
fs.addResponseMiddlware(function(client, request, response, next){
  console.log(response.originalUrl + ' ' + response.statusText);
  next();
});

Response middleware is applied to every response received from the API. Response middleware is a function with the signature (client, request, response, next).

  • client is the instance of the FamilySearch sdk that the request is associated
  • with.
  • request is an object that has {url, method, headers, body}.
  • response is a response object.
  • next is a method that must be called when the middleware is done. Its signature is function(error, cancel). When cancel has any truthy value the response middleware chain is canceled but unlike request middleware the request callback is not called. Cancelling is done when a new request must be issued, such as middleware that handles redirects or throttling. In this case the subsequent request will have it's own middleware chain.

Response middleware is applied in the order that it was added. The SDK sets up some response middleware by default.

Default Middlware

Some request and response middleware is configured by default for processing request bodies, handling throttling, and other default functionality.

At the moment there is no official way to modify the default middleware. Visit issue 6 to voice your support for this functionality and express your opinion on how it should be done.

Objects Instead of Plain JSON

If you would prefer having response bodies deserialized with an object model instead of traversing plain JSON objects then you can register response middleware to use gedcomx-fs-js.

// First you need to setup gedcomx-js and gedcomx-fs-js. See those libraries
// for instructions. Here we will assume they are available in the current
// scope as `GedcomX`.

// Then we register the middleware. When a response has a body, the body is 
// deserialized into an object model provided by gedcomx-js and made available
// on the request via the `gedcomx` attribute.
fs.addResponseMiddleware(function(client, request, response, next){
  if(response.data){
    if(response.data.entries){
      response.gedcomx = GedcomX.AtomFeed(response.data);
    }
    else if(response.data.access_token){
      response.gedcomx = GedcomX.OAuth2(response.data);
    }
    else if(response.data.errors) {
      response.gedcomx = GedcomX.Errors(response.data);
    }
    else {
      response.gedcomx = GedcomX(response.data);
    }
  }
  next();
});

Migrating from v1 to v2

Breaking changes:

  1. The getHeader() and getAllHeaders() response methods were replaced with the headers object.
  2. saveAccessToken now defaults to false instead of true.
  3. The signature of response callbacks changed from function(response) to function(error, response).
  4. In v1 response middleware was called even when a network error occurred. In v2 the response middleware is only called when a response is actually recieved.
  5. Redirects are not automatically followed. Use the followRedirect: true request option to have the SDK automatically follow a redirect response.

fs-js-lite's People

Contributors

dependabot[bot] avatar dotspencer avatar justincy avatar matchlighter avatar misbach 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

Watchers

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

fs-js-lite's Issues

Get a person's information and all their relatives information

I'm following the example:

fs.get('/platform/tree/persons/', {
  body: { persons: [ personData ] }
}, function(error, response){ });

But get:

Uncaught ReferenceError: personData is not defined
    at _thGetFamily (treeHintsDesc.html?code=)
    at treeHintsDesc.html?code=
    at async2.1.5.js:1
    at o (async2.1.5.js:1)
    at async2.1.5.js:1
    at async2.1.5.js:1
    at async2.1.5.js:1
    at Object.<anonymous> (async2.1.5.js:1)
    at _thProcessGeneration (treeHintsDesc.html?code=)
    at treeHintsDesc.html?code=

I would like to retrieve a person, all his information, his spouses, all their information, his children and all their information. Any Suggestions?

Response body not available on cached responses

Response bodies are not being made available on cached responses.

A request is made for a person /platform/tree/persons/PPPP-PPP and is cached because an etag is present on the response. The next time a request is made for that person a 304 with an empty body is returned by the API. In this case, XHR is supposed to return a 200 to JavaScript and include the cached body.

For 304 Not Modified responses that are a result of a user agent generated conditional request the user agent must act as if the server gave a 200 OK response with the appropriate content.

But that's quoting a discontinued spec. The new living spec doesn't immediately appear to address caching.

I'm seeing this behavior on Chrome 54, Firefox 49, and Edge 38/14 on Windows.

Support uploading files for creating memories

FormData makes it really easy to upload files. In the body processing middleware we just need to detect FormData objects and do nothing (don't even set the Content-Type header) so that it gets passed straight to XHR which will correctly process the FormData. Just need to instruct tell users how to properly setup the FormData.

var formData = new FormData();
// Name must be artifact otherwise the API will return a 400
formData.append('artifact', fileBlob);

Are there other usecases we need to support where a blob isn't available?

Simplify OAuth implementation with query params?

OAuth is currently implemented by sending application/x-www-form-urlencoded data in the body. It would be more simple to send the data via query params, plus that might simplify the body request middleware.

Add code coverage

I have a feeling that there are many portions of the SDK that aren't being tested.

Global error callback

Now that response middleware isn't called for request errors, we need a method for registering a global error callback; i.e. a callback that gets called whenever there's a request error (only network errors, not HTTP errors). Response callbacks would still be called. I can think of two use cases this fulfills:

  1. Logging
  2. If your app, client side, wants to show an error toast whenever there's been a problem instead of each component of the app doing it's own error handling (like many web apps do when your connection is flaky and it's trying to reconnect).

Cookie path

The SDK currently sets the cookie path to the current directory (default for the lib it uses). If we're going to force a default I'd rather it be the root directory /.

Should we provide a way to change this?

Change middleware API

Supporting node #11 requires supporting errors #12 which means middlware must also account for them. I believe that we only need to change the behavior of next().

Requests

Request middleware may return an error, a new response, or nothing.

next(error, newResponse)

Responses

Response middleware may return an error or cancel the response middlware chain because a new request was issued (throttling or redirects).

next(error, cancel)

Packaging and publishing

Everything is in one js file to make distribution and packing lite. But it's becoming a pain. It also prevents having a minified version.

Ideally we would find a place to publish that doesn't require committing packages to the git repo.

Error handling

To enable node.js support in #11 we need to change how we handle errors. With just XHR in the client we don't really need to pass around errors objects because when XHR has an error there is nothing to say other than "there's an error." But node.js does have real errors and they need to be accessible to developers.

The only realistic option I see, since we're using callbacks for responses, is to change the response callbacks to follow the node form of function(error, response) where error is undefined when there's no error.

Fetch API

The fetch API defines the global fetch() method for getting resources (modern replacement of XMLHttpRequest which the sdk currently uses internally). Due to browser adoption and usage we won't be able to use it internally any time soon without a polyfill which itself depends on a polyfill for promises. Too much bloat.

But the fetch spec also defines Request, Response, and Headers objects. The sdk has it's own internal representation of those concepts. It might be useful to implement them according to the fetch spec. Though this would be a breaking change since responses are exposed to the developer.

Unhelpful error when request options are undefined

When calling a request method and passing undefined for the request options as the second parameter

fs.get(url, options, function(){ });

Then an unhelpful error is thrown from inside the SDK because the SDK assumes that the options object exists and is an object when three parameters are present on the method. It should either default to an empty options object or throw a helpful error.

Timer issue

With the default timer interval set to 1000, a queue is created and one request per second is sent. All good.

When the queue is empty, clearInterval() is called, but the timer is not set to null.

The next time a request comes in, the request is queued, but the timer fails a null check and is never restarted!

BAsically I can send one flurry of requests, but after the queue is cleared I can't run anymore.

2019-10-31_21-11-42

Disable default middleware

We're almost done with #2 adding middleware. It has been a much more fruitful enterprise than I expected.

As part of that, the sdk sets up default request and response middleware to handle throttling, redirects, Authorization header, default Accept header, and more. It could be useful to provide a method for disabling some of the default middleware.

Idea 1: Options

Allow for the middleware to be disabled via the options when instantiating an SDK instance.

var client = FamilySearch({
    handleThrottling: false
});

Idea 2: Middleware Stacks

This idea comes from Guzzle with has Handlers and HandlerStacks (combination of a handler and middleware stacks).

This would give developers more control over middleware.

Support for self-throttling

We need a way to limit the rate at which API calls will be made. For example. I want to crawl the Family Tree, and I know my code will need to make a lot of API calls. I don't care as much how fast it takes. I just don't want to send a large burst of API calls to FamilySearch all at once.

When FamilySearch gets large sudden spikes or bursts of API traffic it causes server scale up events that can take up to 15 minutes for the servers to spin up. By that time the traffic could be over. So we incurred a cost to scale up our servers for no reason. If we can level out the API traffic bursts then the server scale up events can be minimized.

If we had a self-throttling lever of say an amount of time (1,000 milliseconds etc.) to wait before each API call is made, this would allow partner apps to "play nice" with our API and not send large bursts of requests.

Logout has never really worked properly...

I have tried for both SDK's now to get logout to properly function with the SDK. I call the /platform/logout endpoint and delete my access token. It redirects to ident.familysearch.org only to reauthenticate the existing session.

I would prefer to force a re-auth, but I can't seem to find any documentation for that. Any ideas? Here's my code block for the most recent SDK.

              that.client.post('/platform/logout', {
                Header: {'Authorization': 'Bearer ' + that.client.getAccessToken()},
              }, function (error, response) {
                if (that.client.handleError(error, response) == false) {
                  if (error) {
                    return reject(error);
                  }
                }
                console.log(response);
                that.client.deleteAccessToken();
                delete $rootScope.user;
                return resolve(response);
              });

Support Promises

I've seen some libraries support both callbacks and promises. Let's explore that sometime.

Need to rethink how we handle redirects

The sdk is setup to automatically and transparently handle redirects. It averts the browser's automatic (and sometimes) faulty transparent handling by using the x-expect-override header, though we always include it which has already caused problems (see #10). In addition to that, we don't always want to automatically follow the redirects. For example when requesting a person's portrait /platform/tree/persons/PPPP-PP/portrait the API responds with a 307 redirect to the URL of the actual image but most often we just want the URL and not the image thus we don't want to follow that redirect.

This brings back to #6 disabling default middleware. But in these cases we don't want to disable them completely, we just want to selectively disable or enable. In other words, on each request we want to set an option that enables the x-expect-override header and another option that enables the transparent redirect.

client.get('/platform/tree/current-person', {
    expectRedirect: true,
    followRedirect: true
});

client.get('/platform/tree/persons/PPPP-PP/portrait', {
    expectRedirect: true
    // We don't set the followRedirect option because we don't want to follow 
    // the portrait redirect to the image, we just want the URL as explained above
});

This removes the need for dealing with #6 and waiting on word from FamilySearch about how they're going to respond to the issues described in #10. Though it requires the SDK to make the original request options available to both request and response middleware.

The only downside is that now developers must keep track of which resources might return a redirect. We were originally trying to hide any concern of redirects from the developer it now appears that we can't.

Chrome ERR_BLOCKED_BY_CLIENT with any Ad-Blocker Enabled

When attempting to migrate to the new JS SDK I noticed there's an issue with authentication and ad-blockers with chrome. I narrowed it down to this list:
https://easylist.to/easylist/easylist.txt

For whatever reason, this list is blocking requests to FamilySearch, primarily ident.familysearch.org. We're not doing anything special in the code:

this.client.oauthToken(this.urlParams.code, function (error, tokenResponse) {
                if (error || tokenResponse.statusCode >= 400) {
                  $rootScope.log(error || restError(tokenResponse));
                }
                that.client.setAccessToken(tokenResponse.data.access_token);
                window.location = redirect;
              });

https://cldup.com/8VvcJauq40.png

Any ideas here? The old SDK worked without an issue...

Middleware

While implementing #1 it occurred to me that supporting middleware would create a cleaner interface for optional features and make possible future enhancements easier (such as logging and caching).


Middleware could be registered when the client is created

var client = new FamilySearch({
    middleware: [middleware1, middleware2]
});

or added later

client.addMiddleware(middleware2);

Middleware would be called in the order they were added when processes requests and called in reverse order when processing responses.

Middleware would be an object with request and response properties that are both a function with the following signatures

{
    request: function(request, next){ },
    response: function(request, response, next){ }
}

New response format

The current response format was largely dictated by what is available from XHR. While working on node.js support #11 we might as well revisit it the design to see if there's a way to get better consistency. For example, all data is available as properties except for the headers (because of XHR).

Add oauthRedirectURL()

oauthRedirect() can be used in the browser to have the sdk perform the redirect but that doesn't work in node. Instead we just need to generate the URL.

Extracting information from a response

I'm trying to get the Id and Name of the current user with:

    fs.get('/platform/users/current', {
        headers: { Accept: 'application/x-gedcomx-v1+json' }
    }, function(error, response) {...

I receive:

status code=200
response=[object Object]
response.body={ "users" : [ { "id" : "cis.user.MMMM-6C38", "contactName" : "tum000148389", "helperAccessPin" : "14838", "givenName" : "Silvin", "familyName" : "Hotler", "email" : "[email protected]", "country" : "USA", "gender" : "MALE", "birthDate" : "02 Feb 1985", "preferredLanguage" : "en", "displayName" : "Silvin Hotler", "personId" : "KWH1-1N5", "treeUserId" : "MMXX-561", "links" : { "person" : { "href" : "https://integration.familysearch.org/platform/tree/persons/KWH1-1N5" }, "self" : { "href" : "https://integration.familysearch.org/platform/users/current" }, "artifacts" : { "href" : "https://integration.familysearch.org/platform/memories/users/cis.user.MMMM-6C38/memories" } } } ] }
response.body.users=undefined

How to I extract the Id and Name?
testLite.txt

Add built in support for node.js

The SDK can work in node.js but it requires a shim for XMLHttpRequest. That might require a breaking change in the format of responses because we modeled responses after what was available from XHR.

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.