Giter VIP home page Giter VIP logo

fusionauth-react-sdk's People

Contributors

bhalsey avatar colinfrick avatar david-chalk avatar davidprae avatar dmackinn avatar jakelo123 avatar jpdeblock avatar michaelyons avatar mooreds avatar osbornm avatar sam-woodridge avatar todda00 avatar vcampitelli avatar

Stargazers

 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

fusionauth-react-sdk's Issues

Provider needs error handling

Provider needs error handling

Description

The main provider component -- FusionAuthProvider -- doesn't check if the response from /me is good. If someone had a server implementation that returned an object representing an error, the SDK would set it as the user.

Probably just need to check for an ok http status from call to /me, and take the correct action from there.

Affects versions

All versions.

Steps to reproduce

Using the example-react-sdk:

  1. Go to the /me route handler and make it do something like:
res.status(500).send({
    error: 'something went wrong'
})

Then save and boot up the server and client.
2. Go to the browser and log in. If the browser doesn't make the network call to (/me), then clear the user cookie with dev tools and refresh.
3. Observe that the SDK is showing you the /account page, but there is no user info to display because your user is set to { error: 'something went wrong' })

Expected behavior

The SDK should know that it is in a non-authenticated state and set the user to {}

Platform

Any web browser.

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

customize the name of the `isAuthenticated` cookie

Option to customize the cookie name used to check isAuthenticated.

Problem

We're using a different backend implementation, our cookie content and name is different, so isAuthenticated will never work for us.

Solution

Have an option to change the cookie name from app.at_exp to something else, so the SDK could still be used as is.

Alternatives/workarounds

Don't use the SDK and implement our own SDK which is also OK.

Specify node/npm version

package.json should specify a node and npm version

This is important for compatibility because mismatched versions can create unexpected issues.

Work with FusionAuth core hosted endpoints

Work with FusionAuth core hosted endpoints

Problem

Using the React SDK requires hosting server endpoints. The only compatible solution right now is in https://github.com/FusionAuth/fusionauth-example-react-sdk

Solution

When FusionAuth adds functionality to host these endpoints, the react sdk should work with this out of the box.

Alternatives/workarounds

Use the example-react-sdk / server

Additional context

FusionAuth/fusionauth-issues#1943

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

How to vote

Please give us a thumbs up or thumbs down as a reaction to help us prioritize this feature. Feel free to comment if you have a particular need or comment on how this feature should work.

[RFC] Refactor FusionAuthProvider

NOTE: this issue tracks the discussion around the decision to even do this. This is not the implementation issue.

Situation

The FusionAuth React SDK has a FusionAuthProvider component that is responsible for much of the functionality. It's basically the valuable intellectual property of the SDK.

Challenge

The FusionAuthProvider source code is a bit unwieldy and difficult to digest for developers. This is turn can make it difficult to debug or just generally understand what "state" the running code is in. Extending or adding new functionality can also be a precarious operation.

Question

Should we refactor the FusionAuthProvider to improve the readability and maintainability of the code?

Answer

Yes. And let's use XState to model the business logic and state of the FusionAuthProvider.

What is XState?

XState is a state management and orchestration solution for JavaScript and TypeScript apps. It lets us create state machines which offer predictability via guarantees about state and functionality.

Sounds cool but what benefits will this offer us?

Here's an example we can look at.

The example demonstrates a few nifty features:

1. Discrete, exclusive states

In in the world of state machines, the term "state" refers to a "state of being" (as opposed to "data" – this is a common conflation of terms). State machines can only be in one state at a time. In the case of lampMachine, the defined states are TurnedOn and TurnedOff. So the lampMachine can either be TurnedOn or TurnedOff. There's no way to be in both or neither. This is great because it exactly models the binary state lamps actually have.

How is this applicable to the FusionAuth React SDK?

There are two super important states to consider within the FusionAuth ReactSDK: 1) "user is authenticated" and 2) "user is not authenticated". If a user is authenticated they cannot also be unauthenticated (and vice versa). Boolean values can represent this simple idea easy enough. But things get hazy once we introduce related states.

Take this code from the React SDK for example:

const [isLoading, setIsLoading] = useState(false);
const isAuthenticated = useMemo(() => Object.keys(user).length > 0, [user]);

So it turns out there's some more "states" we can be in. How do they interact? What's the relationship? Do they affect one another? Does isLoading have something to do with isAuthenticated?

Further, it's possible for isLoading and isAuthenticated to both be true. Is that ok? It's really hard to tell, we need a lot more context which requires digging into the code.

This demonstrates one of the big benefits of state machines: I don't need any more context than reading the defined states to understand what's even possible. Intention is clearly encoded when using state machines.

2. Scoped events

State machines have the ability to scope events to particular states. In the lampMachine, if it's in the TurnedOn state, sending an ON event won't do anything because we didn't define that event for that state. This matches our mental model of how lamps work: if a lamp is turned on, I can't turn it on again. I could try but I couldn't actually do anything since there is nothing to do.

How is this applicable to the FusionAuth React SDK?

When a user is a particular state like Authenticated, we can make sure events that don't contextually mean anything (like AUTHENTICATE) actually do anything at all. We'll be guaranteed to not try and authenticate again.

3. Portable business logic

While there is a small integration API to deal with when consuming state machines, the actual business logic is framework agnostic so can be used in arbitrary JavaScript projects.

How is this applicable to the FusionAuth React SDK?

This means we can build out this robust model of business logic and immediately bring it over to the other SDKs and it'll work exactly the same. This helps maintainability and coherence among the SDKs – a fix for one fixes the others, extending functionality for one, extends that functionality for the others.

What's the lift? 💪

The current business logic isn't very explicit so we will need to be extra careful about interpreting the business logic, extracting it, and translating it into a state machine. We already have built up a good mental model about what's going on but refactors like this have the potential to uncover ambiguity in the current implementation. We'll need to eliminate these ambiguities by making decisions/uncovering what the actual product requirments are. This could involve some back-and-forth or even a meeting.

Estimate: 1 developer 2-3 days

Support localstorage

Feedback from a community user:

I won’t be able to use it yet, because my auth flow is traditional i.e. Saving the tokens to localstorage

Source Maps Missing

Issue

The source code published to NPM is compressed and no source maps are shipped. This makes it really difficult to debug.

FWIW; A lot of libraries these days don't even do compression since it will be handled by the users setup ( webpack ) already.

Tests causing 'not wrapped in act(...)' warning

Running the tests will output several 'not wrapped in act(...)' warnings.

Description

The warning is being triggered by some tests calling testing-library's render method with await as well as some instances from within act. This is an indication that react state of components being tested may be out of sync with assertions made in tests, which could cause unexpected behavior.

Relevant documentation

image

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

Do not use multiple package managers

We've noticed that some repos contain both yarn.lock and package-lock.json files -- a result of using both yarn and npm package managers. This item is to make sure repositories have just one lock file. Document this information in any relevant README files, so that future contributors will know which package manager to use.

/me fetch failure causing infinite loop

/me fetch failure causing infinite loop

Description

After login when no user cookie is present then the user info endpoint is being invoked via a fetch request and if that fetch fails for a reason (CORS, or anything else), and a re-render is triggered, then it will cause an infinite loop (can be tested with the example app, comment out the alert dialog to see it).

Affects versions

Latest

Steps to reproduce

Steps to reproduce the behavior:

  1. Configure a failing userinfo endpoint in the example app
  2. Login to the app

Expected behavior

Have some backoff strategy to stop making the fetch calls and getting information about userinfo failure specifically (because it is a backchannel invocation failure, not a redirect failure) or the ability to pass in a function which can do the userinfo invocation, so the consumers of the SDK has a better control over what to do.

Screenshots

N/A

Add peerDependencies

Add peerDependencies

Description

From a feedback request:

You will want to include “react” and “react-dom” as peerDependencies, if that is the package that is being released.

Expected behavior

Include these in the package.json so that if you try to install the react sdk without react, npm complains.

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

Additional context

https://flaviocopes.com/npm-peer-dependencies/

Send down id token to the react SDK

Send down id token to the react SDK

Problem

We may want to display user information in the react app. The proper way to do that is to use an id_token.

Solution

We should send the id token down to the react app as a non http only cookie.

Alternatives/workarounds

The user object may have that data. See #22 as well.

Additional context

Add any other context or screenshots about the feature request here.

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

How to vote

Please give us a thumbs up or thumbs down as a reaction to help us prioritize this feature. Feel free to comment if you have a particular need or comment on how this feature should work.

Why is `Content-Type` set for empty requests?

Currently when making a POST to the /jwt-refresh endpoint the request gets a content-type set to application/json (see: https://github.com/FusionAuth/fusionauth-react-sdk/blob/main/src/providers/FusionAuthProvider.tsx#L141-L149). This seems odd since there is no BODY on the request. This can cause problems in some backends as they might choose to valid a body based on the the content-type. For example the default JSON parser in fastify (a node backend) throws if there is no body (see: https://github.com/fastify/fastify/blob/a4b21f0a2e7e753e9a8772dbac6b255b7dcbdc6b/test/helper.js#L316-L335).

I see to possible solutions to this.

  1. Don't set the content-type
  2. If the idea was to set this to eventually pass something in the body but that was never implemented you could just set the body as an empty object for now.

Please let me know your thoughts.

[RFC] Utilize Storybook for UI development

NOTE: this issue tracks the discussion around the decision to even do this. This is not the implementation issue.

Situation

The FusionAuth React SDK is a React npm package that offers both UI components with preconfigured functionality and non-UI components that offer non-visible functionality. There is currently no way to view the visible UI components.

Challenge

It is difficult to work on the visual styles of the UI components because there is no way to view them.

Question

How can we view the UI components in order to get visual feedback on style changes?

Answer

Let's use Storybook. It is the industry standard for viewing UI components in isolation and as such is well-supported with lots of documentation and resources.

Rendering components in isolation is particularly necessary for UI libraries like the FusionAuth React SDK because we don't have a host app to run them in. Components being isolated also forces developers to make sure they don't have any hidden dependencies to work correctly.

Aside from giving developers a place to develop UI components, Storybook also acts as interactive documentation for these components. Developers (and others) can interactively change props and components update to reflect their changes.

FusionAuth could choose to host the Storybook publicly so developers can view the components without even pulling down and running the project themselves. Services like Chromatic make hosting Storbook instances extremely easy and quick.

We can also "compose" Storybooks enabling us to have one place to view the components for every FusionAuth SDK. This would be a future enhancement past this one however.

Example Storybooks

What's the lift? 💪

Integrating Storybook is fairly simple. And because we don't have a ton of components, it shouldn't take very long to make a "Story" for each component.

Estimate: 1 developer 1 day

Update README to mention hosted endpoint support

Update README to mention core hosted endpoint support

Description

The SDK comes with out-of-the-box support for FusionAuth hosted endpoints, however, the README currently says “…requires you to set up a server that will be used to perform the OAuth token exchange. This server must have the following endpoints…”

We should keep the documentation up to date by mentioning (alongside the example server code) that the SDK supports the core endpoints by default, and that hosting your own server is not a requirement. Link to the quickstart example app

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

Add 'change password' button and function

Changing a password is part of the login flow. We should add a button/function to do this. It will redirect the user over to FusionAuth and then, on successful change, send them back to the app.

Remove `async` from non-async function definitions

Remove async from non-async function definitions

Description

The login, logout, and register methods provided by IFusionAuthContext are incorrectly typed as asynchronous. They are defined with async, even though they do not contain any asynchronous code. It would be helpful if tests calling these functions with await and waitFor(() =>... assertion) were also be updated.

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

refreshToken() does not work without implicitly setting tokenRefreshPath

refreshToken() does not work without implicitly setting tokenRefreshPath

Description

When using the Hosted Backend, the endpoint URL for token refresh is /app/refresh/{clientId} but the React SDK is sending the refresh attempt to /app/refresh.

When the provider is set with a tokenRefreshPath which includes the clientId, the refresh works as expected.

Not working:

<FusionAuthProvider
  clientID="4dff639c-d360-427a-b742-d108a2229002"
  serverUrl="http://localhost:9011"
  redirectUri="http://localhost:4000"
>

Working:

<FusionAuthProvider
  clientID="4dff639c-d360-427a-b742-d108a2229002"
  serverUrl="http://localhost:9011"
  redirectUri="http://localhost:4000"
  tokenRefreshPath="/app/refresh/4dff639c-d360-427a-b742-d108a2229002"
>

Affects versions

"@fusionauth/react-sdk": "^0.25.0"
Fusion Auth w/ Hosted Backend API @ 1.47.1

Expected behavior

I expect the clientId to be included in the refresh URL without having to set the tokenRefreshPath

v1.0.5 doesn't contain any code

v1.0.5 doesn't contain any code

Description

Cannot import anything, as there's no code listed under node_modeles/@fusionauth/react-sdk

Affects versions

List the version you are running, and any other versions you have attempted a recreated.

Steps to reproduce

  1. install @fusionauth/react-sdk
  2. try to import something from the package

Expected behavior

Package should contain code

Screenshots

image

Platform

(Please complete the following information)

  • Device: Desktop
  • OS: NixOs
  • All browsers
  • PostgreSQL

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

Additional context

Workaround is to install v1.0.0

Add support for automatic, transparent refresh of access tokens

Add support for automatic, transparent refresh of access tokens

Problem

Currently, if I am making requests of an external API who is using the access token retrieved from the OAuth server as a credential, I have to catch a failure/access denied request, and then manually call the refresh token endpoint, then repeat my call.

Solution

It would be great if the SDK automatically renewed the access token without any work on my part.

One option would be to have the server side SDK code send down a cookie that expired just before the access token did. This cookie could contain no secrets, so it could be readable by JS. We could set a timer to check for this cookie regularly. When the cookie is gone/expired, the SDK js could call out to the refresh token endpoint and get a new access token.

There may be other approaches that would work.

Alternatives/workarounds

n/a

Additional context

FusionAuth/fusionauth-issues#1674 outlines this functionality as well.

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

How to vote

Please give us a thumbs up or thumbs down as a reaction to help us prioritize this feature. Feel free to comment if you have a particular need or comment on how this feature should work.

[RFC] Utilize Mock Server Worker for tests

NOTE: this issue tracks the discussion around the decision to even do this. This is not the implementation issue.

Situation

The FusionAuth React SDK currently extensively uses function mocking in order to test functions that access the network.

Challenge

While this ensures that a function by a particular name is called, it isn't a very robust way to check that a function does what it's supposed to do.

Question

How can we test functions that access the network in a more robust way that gives us more confidence in the results of our tests?

Answer

Let's use Mock Service Worker (MSW). MSW is the industry standard API mocking library that allows you to write client-agnostic mocks and reuse them across any frameworks, tools, and environments. It's well supported and has plenty of resources and documention.

Basically, we can utilize it to setup Service Workers that intercept network requests and respond with what we want to test. I.e. a function that accesses GET /some-endpoint won't need to be mocked. It will make it's request normally and the Service Workers will intercept the call and respond accordingly.

We're able to easily test multiple response types as well, so we can make sure any network failures are handled in an appropriate way as well.

This will let the code run that should run and the surface area of our tests will be increased and so will the accuracy of our tests.

MSW is also framework agnostic which means we could reuse the work we do for the other FusionAuth SDKs, increasing our confidence in those tests as well.

What's the lift? 💪

The FusionAuth React SDK doesn't hit very many endpoints (about 6), so even accounting for variations, we're not looking at too much setup time.

After setup, we just need to update the tests to remove the mocked calls. This also won't be very time consuming as there aren't many functions to worry about.

Estimate: 1 developer 1 day

Access token not removed after logout

Related: #22

The access and refresh token cookies are set by the example server side code. Since it is set by a server side component, it is not accessible to the react SDK to delete on logout.

When you click logout, you are sent to FusionAuth's logout link: https://github.com/FusionAuth/fusionauth-react-sdk/blob/main/src/providers/FusionAuthProvider.tsx#L100

This kills the FusionAuth managed cookies. We also need to remove any server side non FusionAuth set cookies, such as the access_token.

After the FusionAuth logout URL is processed, it calls the logout url. (Set here by default; https://github.com/FusionAuth/fusionauth-example-react-sdk/blob/main/kickstart/kickstart.json#L60 ). That'd the proper place to nuke the access token and refresh token cookies.

So I think the best path is to create another server side route which receives the logout request, nukes the cookies, and then redirect to the first, unauthenticated page of the react app.

Add 'Unauthenticated' component

Add 'Unauthenticated' component

We have an AuthRequired component, but it is common to want to display things (the login button) only to unauthenticated users.

Solution

Add an inverse component that only executes when a user is not authenticated.

Alternatives/workarounds

use the isAuthenticated function.

Additional context

Add any other context or screenshots about the feature request here.

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

How to vote

Please give us a thumbs up or thumbs down as a reaction to help us prioritize this feature. Feel free to comment if you have a particular need or comment on how this feature should work.

No error message when the wrong client id is provided

No error message when the wrong client id is provided

Description

There is no error message to the developer if they configure this SDK with the wrong client id, just a blank screen.

Affects versions

0.25.0
using fusionauth 1.45.1 hosted backend.

Steps to reproduce

Steps to reproduce the behavior:

  1. Configure a new react app with this library.
  2. Create an Application in FusionAuth
  3. Put the wrong client id in the react app createRoot call.
  4. Add a login button to the app
  5. Try to login using an incognito browser.

Expected behavior

I don't know, but at least a message in the JS console.

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

Additional context

You can grab this sample app if you want a super simple testbed: https://github.com/FusionAuth/fusionauth-example-react-guide

User object from the SDK is not set

The issue I am seeing is that the user object from the sdk is just an empty object. The “isAuthenticated” property is being correctly populated though.

    const { user, isAuthenticated,  } = useFusionAuth();

This results in user being set to an empty object: {} and isAuthenticated being true.

I am able to get the user by just reading the cookie myself, but it seems like user should be set.

It looks like setUser is configured here: https://github.com/FusionAuth/fusionauth-react-sdk/blob/main/src/providers/FusionAuthProvider.tsx#L56

and it seems like useEffect should be setting the user object here: https://github.com/FusionAuth/fusionauth-react-sdk/blob/main/src/providers/FusionAuthProvider.tsx#L127 but the useFusionAuth method doesn't return a valid user.

Inline documentation for SDK

Inline documentation for SDK

Problem

There is no inline documentation for components and methods provided by the SDK

Solution

Use JSDoc to include inline docs so that developers get useful descriptions in tooltips, like the screenshot below (pulled in from our Angular SDK).

image

Additional context

We can align requirements/documentation across the react/angular/vue SDKs using our tech spec -- cc: @david-chalk

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

How to vote

Please give us a thumbs up or thumbs down as a reaction to help us prioritize this feature. Feel free to comment if you have a particular need or comment on how this feature should work.

Token exchange endpoint getting called multiple times

I am trying to recreate the example using a Vite/React app with a Serverless/Lambda REST API. I have the POST /token-exchange endpoint doing exactly as it does in the example server, and every time I try to login without an access_token cookie, it is calling the token-exchange endpoint twice. This causes it to error out since it's a race to whoever gets to claim the code first. Sometimes one call will succeed and the other will fail, other times both will fail. But never will both succeed (as they shouldn't). Not sure how much it helps as it's a very local setup, but here are the logs from my Docker container (note the first call succeeds and gives me a token and gets the user info, but the second call fails with an auth_code_not_found error):

fusionauth                     | 2022-12-16 08:43:27.606 PM WARN  org.elasticsearch.client.RestClient - request [PUT http://search:9200/fusionauth_user/_doc/cf82193b-56b6-4740-a75b-0bc1d8ee38fd] returned 1 warnings: [299 Elasticsearch-7.17.0-bee86328705acaa9a6daede7140defd4d9ec56bd "Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone. See https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html to enable security."]
forms-services                 |
forms-services                 |
forms-services                 | POST /token-exchange (λ: tokenExchange)
forms-services                 |
forms-services                 |
forms-services                 | POST /token-exchange (λ: tokenExchange)
forms-localstack               | 2022-12-16T20:43:32.845  INFO --- [   asgi_gw_0] localstack.request.aws     : AWS secretsmanager.GetSecretValue => 200
forms-services                 | user info ClientResponse {
forms-services                 |   statusCode: 200,
forms-services                 |   response: {
forms-services                 |     applicationId: <redacted>,
forms-services                 |     email: <redacted>,
forms-services                 |     email_verified: true,
forms-services                 |     family_name: 'Dura',
forms-services                 |     given_name: 'Josh',
forms-services                 |     roles: [],
forms-services                 |     scope: 'openid offline_access',
forms-services                 |     sid: <redacted>,
forms-services                 |     sub: <redacted>,
forms-services                 |     tid: <redacted>
forms-services                 |   }
forms-services                 | }
forms-services                 | (λ: tokenExchange) RequestId: 6f693574-2c1c-4879-b922-a1d1cf658d73  Duration: 5184.44 ms  Billed Duration: 5185 ms
forms-localstack               | 2022-12-16T20:43:33.032  INFO --- [   asgi_gw_2] localstack.request.aws     : AWS secretsmanager.GetSecretValue => 200
forms-services                 | error with oauth2/token endpoint call {"statusCode":400,"exception":{"error":"invalid_request","error_description":"Invalid Authorization Code","error_reason":"auth_code_not_found"}}
forms-services                 | (λ: tokenExchange) RequestId: 00c5005e-59f9-4a1e-a758-7a8c6b71945b  Duration: 5250.92 ms  Billed Duration: 5251 ms
forms-services                 | error Error: [object Object]
forms-services                 |     at /source/.build/utils/fusionauth.js:87:27
forms-services                 |     at step (/source/.build/utils/fusionauth.js:33:23)
forms-services                 |     at Object.throw (/source/.build/utils/fusionauth.js:14:53)
forms-services                 |     at rejected (/source/.build/utils/fusionauth.js:6:65)
forms-services                 |     at processTicksAndRejections (node:internal/process/task_queues:96:5)

allow withRole to take an array of values

allow withRole to take an array of values

Problem

A developer might want to make a page available to multiple roles. RequireAuth.withRole only takes a single value.

Solution

Allow withRole to take a single value or array of values. It should be typed, anyways. So something like withRole: String | String[]

Alternatives/workarounds

FusionAuth role configuration can be set to add hybrid roles that are the combined set of the multiple roles. But this is cumbersome and might not be possible for all users.

Additional context

Requested by Sonderformat.

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.