Giter Club home page Giter Club logo

dendrite's Introduction

Dendrite

Build status Dendrite Dendrite Dev

Dendrite is a second-generation Matrix homeserver written in Go. It intends to provide an efficient, reliable and scalable alternative to Synapse:

  • Efficient: A small memory footprint with better baseline performance than an out-of-the-box Synapse.
  • Reliable: Implements the Matrix specification as written, using the same test suite as Synapse as well as a brand new Go test suite.
  • Scalable: can run on multiple machines and eventually scale to massive homeserver deployments.

Dendrite is beta software, which means:

  • Dendrite is ready for early adopters. We recommend running Dendrite with a PostgreSQL database.
  • Dendrite has periodic releases. We intend to release new versions as we fix bugs and land significant features.
  • Dendrite supports database schema upgrades between releases. This means you should never lose your messages when upgrading Dendrite.

This does not mean:

  • Dendrite is bug-free. It has not yet been battle-tested in the real world and so will be error prone initially.
  • Dendrite is feature-complete. There may be client or federation APIs that are not implemented.
  • Dendrite is ready for massive homeserver deployments. There is no high-availability/clustering support.

Currently, we expect Dendrite to function well for small (10s/100s of users) homeserver deployments as well as P2P Matrix nodes in-browser or on mobile devices.

If you have further questions, please take a look at our FAQ or join us in:

Requirements

See the Planning your Installation page for more information on requirements.

To build Dendrite, you will need Go 1.20 or later.

For a usable federating Dendrite deployment, you will also need:

  • A domain name (or subdomain)
  • A valid TLS certificate issued by a trusted authority for that domain
  • SRV records or a well-known file pointing to your deployment

Also recommended are:

  • A PostgreSQL database engine, which will perform better than SQLite with many users and/or larger rooms
  • A reverse proxy server, such as nginx, configured like this sample

The Federation Tester can be used to verify your deployment.

Get started

If you wish to build a fully-federating Dendrite instance, see the Installation documentation. For running in Docker, see build/docker.

The following instructions are enough to get Dendrite started as a non-federating test deployment using self-signed certificates and SQLite databases:

$ git clone https://github.com/matrix-org/dendrite
$ cd dendrite
$ go build -o bin/ ./cmd/...

# Generate a Matrix signing key for federation (required)
$ ./bin/generate-keys --private-key matrix_key.pem

# Generate a self-signed certificate (optional, but a valid TLS certificate is normally
# needed for Matrix federation/clients to work properly!)
$ ./bin/generate-keys --tls-cert server.crt --tls-key server.key

# Copy and modify the config file - you'll need to set a server name and paths to the keys
# at the very least, along with setting up the database connection strings.
$ cp dendrite-sample.yaml dendrite.yaml

# Build and run the server:
$ ./bin/dendrite --tls-cert server.crt --tls-key server.key --config dendrite.yaml

# Create an user account (add -admin for an admin user).
# Specify the localpart only, e.g. 'alice' for '@alice:domain.com'
$ ./bin/create-account --config dendrite.yaml --username alice

Then point your favourite Matrix client at http://localhost:8008 or https://localhost:8448.

Progress

We use a script called "Are We Synapse Yet" which checks Sytest compliance rates. Sytest is a black-box homeserver test rig with around 900 tests. The script works out how many of these tests are passing on Dendrite and it updates with CI. As of January 2023, we have 100% server-server parity with Synapse, and the client-server parity is at 93% , though check CI for the latest numbers. In practice, this means you can communicate locally and via federation with Synapse servers such as matrix.org reasonably well, although there are still some missing features (like SSO and Third-party ID APIs).

We are prioritising features that will benefit single-user homeservers first (e.g Receipts, E2E) rather than features that massive deployments may be interested in (OpenID, Guests, Admin APIs, AS API). This means Dendrite supports amongst others:

  • Core room functionality (creating rooms, invites, auth rules)
  • Room versions 1 to 10 supported
  • Backfilling locally and via federation
  • Accounts, profiles and devices
  • Published room lists
  • Typing
  • Media APIs
  • Redaction
  • Tagging
  • Context
  • E2E keys and device lists
  • Receipts
  • Push
  • Guests
  • User Directory
  • Presence
  • Fulltext search

Contributing

We would be grateful for any help on issues marked as Are We Synapse Yet. These issues all have related Sytests which need to pass in order for the issue to be closed. Once you've written your code, you can quickly run Sytest to ensure that the test names are now passing.

If you're new to the project, see our Contributing page to get up to speed, then look for Good First Issues. If you're familiar with the project, look for Help Wanted issues.

dendrite's People

Contributors

0x1a8510f2 avatar aditsachde avatar anoadragon453 avatar apwhitehat avatar ara4n avatar babolivier avatar behouba avatar bn4t avatar bodqhrohro avatar brianathere avatar cnly avatar cromfr avatar dependabot[bot] avatar devonh avatar erikjohnston avatar fantashley avatar genofire avatar half-shot avatar kegsay avatar l2dy avatar lesterpig avatar negativemjark avatar neilalexander avatar piotrkozimor avatar prateek2211 avatar richvdh avatar s7evink avatar superdump avatar swedgwood avatar t3chguy 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  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

dendrite's Issues

Handle edge cases when rejecting invites over federation.

The syncapi needs to handle the case where we reject an invite for a room the server is not a member of over federation.

Currently the syncapi detects when an invite has been rejected by waiting for a leave event in the roomserver output stream. However when we reject an invite for a room the server isn't a member of it doesn't have enough information to write an ordinary leave event as a OutputNewRoomEvent to the roomserver output stream. (It won't know the state of the room for example).

Instead it writes an OutputRetireInviteEvent to the output stream. The syncapi needs to listen for those messages and inform the clients that the invite event has been rejected using it.

Message sending checklist

Bare minimum stuff to do to send messages through the system (no auth etc):

  • Add /createRoom endpoint (don't bother with http auth, send to room server) #32 #33
  • Add /send endpoint (don't bother with http auth, send to room server) #39
  • Add /state endpoint (don't bother with http auth, send to room server) #41

Implement /logout/all

This should be a simple extension of the /logout API, but that logs out all devices rather than just the current one.

Add component for sending messages to remote servers over federation.

In gomatrixserverlib:

  • Add function for signing HTTP requests using a local ed25519 server key,

Add a new component to dendrite:

  • Consume the kafka topic for output room events from the room server.
  • Track which remote servers are in each room.
  • Add new events to a queue of events to send to each server that is in the room.
  • Send a /_matrix/federation/v1/send request for each server with events in their queue.
  • Remove the events from the queue on success.
  • Bonus: implement retries and back off when servers go down.

[query] Migration plan from Synapse to Dendrite

User story: I am a Synapse service provider with many active users. When Dendrite is released I want to migrate my service to Dendrite, because it will be a superior solution.

Query: Is there (going to be) a migration plan for Synapse instances wanting to move to Dendrite? Are there any Synapse configuration options I can set today that will make that future migration faster/easier/feasible?

(This query is primarily about the service provider, not about self-hosted homeservers. I guess it is worth considering that users may be connecting to my instance with their own homeservers.)

Add new linters

We should add the following linters, but they require fixing up current violations:

  • errcheck check that we are checking error return values
  • vet this checks a bunch of stuff, but currently fails as we don't always create we don't always create composite struct literals with keys syntax.
  • dupl/goconst check that we're not duplicating code or constants
  • gosimple checks if code could be simpler (part of megacheck)
  • staticheck checks things like checking error on file.Close, if we're using deprecated APIs, etc (part of megacheck)
  • unused if there are any unused "stuff" (part of megacheck)
  • megacheck does the previous three tests in one pass, so more efficient than enabling them independently
  • unparam are there any unused params?
  • safesql check that our sql statements are statically "safe"

Change repo root

Currently the root of this repository is equal to the systems $GOPATH.

I sugest you move it to the actual project folder $GOPATH/src/github.com/matrix-org/dendrite.

The $GOPATH path contains (all) Go-code on a system and moving the repository root would allow for intended usage of go get.

Please read this guide on the matter: Code organization

Add component for receiving federated messages from other servers.

Add support for handling /_matrix/federation/v1/send/... requests.

In gomatrixserverlib:

  • Add functions for fetching ed25519 server keys from the perspective servers.
  • Add functions for authenticating federated HTTP requests using the ed25519 server keys.
  • Add functions for verifying ed25519 signatures on events.
  • Add functions for signing outbound federated HTTP requests using a local ed25519 server key.
  • Add functions for requesting state at an event from a remote matrix server over federation.

In dendrite add a new component for receiving events:

  • Add /_matrix/federation/v1/send/... HTTP APIs
  • Check the signatures on the HTTP request.
  • Check the signatures on each matrix event in the request.
  • Query the roomserver to ensure we have state at each event.
  • If we have the state at the event then write the event to input room event kafka topic.
  • If we don't have the state at the event then fetch the state from the remote server.
  • Bonus: Backfill missing events from the remote server if there is a small gap between the new event and the events we have on the server.
  • Bonus: Ratelimit the incoming /send requests for each server.

Implement filter APIs

Implement /_matrix/client/r0/user/../filter HTTP APIs for storing filters.

In Client API component:

  • Add the HTTP APIs.
  • Store the filters in a postgres database that can also be accessed by the Sync component.

In the Sync Server component:

  • Load the filter from the postgres database.
  • Use the filter when generating the /sync response.

Handle redacted events

We currently just load the full event when calculating /sync responses. We should be keeping track of redactions and then loading up redacted versions where necessary, including the logic for "oh I told you the full event before, here's the redaction event now" vs "I never told you about the event, so here's just the redacted version of it".

Handle limited syncs and back-pagination

We don't currently set limited correctly:

src/github.com/matrix-org/dendrite/syncapi/sync/requestpool.go:165:		jr.Timeline.Limited = false // TODO: if len(events) >= numRecents + 1 and then set limited:true

We also don't ever set back-pagination tokens on room responses.

Basic GET /devices endpoint

Add GET /devices and GET /device/{deviceID} endpoint.

The response for /devices should look something like:

{ "devices": [
    { "device_id": ..., }.
    { "device_id": ..., }.
] }

In the future we'll also want to add other information, such as device display name, last seen, etc, but that can be added at a later time

client-api-server can race with roomserver when querying latest events & state (and constant queries are inefficient)

I was surprised that whenever I send a message on client-api-server, it first calls QueryLatestEventsAndState on the roomserver to get latest events & state. This seems inefficient as we're having to do another HTTP hit before we even add anything to the kafka log. It also seems to provide a race between the roomserver being queried by HTTP and the kafka entry actually getting consumed by the roomserver.

I'd expect that we might need some way to mitigate the race - e.g. putting something in the kafka log to describe the state that the client-api-sever was going on when it sent the event.

Separately, i wonder if there's scope for doing some kind of predictive optimisation on the client-api-server to avoid it having to constantly query the roomserver state all the time.

Specify the HTTP methods of endpoints in the routers

It's currently impossible to know with which HTTP method an endpoint must be requested for most endpoints in Dendrite's code base. This can make the API hard to understand and to work with, especially for endpoints that haven't been added to the spec yet (as the only "doc" left for those is Synapse's code base).

mux allows to specify allowed methods using Route.Methods(), as used here. Using it on every endpoint would improve the routes' readability in the code base and prevent a client from requesting one of them using an unsupported method.

Some routes are already using this, my proposal would be to extend this to every route in Dendrite.

Track whether our server is in the room.

For each room in the room server we need to know whether our server is still in the room.
Otherwise horrible things will happen if we try to rejoin a room the server has left.
The client API will use this information to tell whether a user can join the room locally or if it will need to contact another server. If this information is wrong it will try to rejoin locally using the stale room state.
We need to know this information in the roomserver to tell if our forward extremities are still valid, otherwise the roomserver will try to merge the stale state with the current state.

Implementing this could have some overlap with #215

If we want to support vhosting then this should reflect whether any of the servers we are are in the room.

Sync optimisations

Had a chat with @NegativeMjark about some relatively easy quick-wins the sync server should probably do from the get-go performance wise. They are as follows:

  • Coalesce duplicate /sync requests so you don't do the same work twice.
  • Only wake up goroutines you need to wake up, don't wake everyone up. - #101
  • Map room_id -> goroutines when a user first calls /sync and then do deltas via joins/leaves. #101
  • Map user_id -> goroutines as well for things like invites to rooms. #101

Additional optimisations may include:

  • Partitioning the output_room_events table and the current_room_state tables into N partitions. We can't realistically partition into 1 table per room because the sync token really should be bounded and currently the token is the row number it has got up to (albeit we could indirect tokens via a tokens table). It might be better just to split into say 10-20 tables which keeps the token size relatively bounded and gives us some perf wins. As always, profiling will be needed to work out the sweet-spot value of N.

Send room state on join

/sync mandates that when a user joins a room they are told the current state of said room, even if they knew it before (because they previously left). We need to implement that.

Consistent names for the kafka topic environment variables.

Wherever a component refers to a kafka topic either for input or output it should use the same environment variable name for it.

So for example clientapi and roomserver read the same topic from CLIENTAPI_OUTPUT_TOPIC and TOPIC_INPUT_ROOM_EVENT respectively. They should both be reading it from the same environment variable.

Figure out how to do stream positions in the syncserver

Right now its a hack, so we should look into getting something proper in place before we build too much on top of it.

Do we want to allow users to switch from one instance of the sync server to another? This would allow us to auto fail over if one of the sync servers died, but would entail having stream positions be globally defined, rather than specific to a particular instance.

We also need to figure out how to do this efficient with respect to database design.

Implement the media repository APIs

The media repository component handles /_matrix/media/v1/ API requests from local clients and remote servers. It can create thumbnails for common image formats like JPEG, GIF and PNG.

See Component-Design for general guidelines for writing a component.

  • Handle /_matrix/media/v1/upload requests
    • Store the content. Decide whether to use a local fs, or something else to store the content.
    • Authenticate the user. Decide whether to use postgres, or an internal API, or a timebound macaroon to check the access token.
    • Maybe look at https://github.com/erikjohnston/gotest/tree/test
  • Handle /_matrix/media/v1/download/... requests
  • Handle /_matrix/media/v1/thumbnail/... requests
  • Handle /_matrix/media/v1/preview_url/... requests
  • Think about sharding for the media repository.
  • Think about replication for the media repository.

More efficient query of if server can see event

Currently we implement the question of "can a server see an event" by pulling out the state at the event (and potentially the current state). This is probably needlessly inefficient, especially as its currently in the room server.

Remove reader/writer split

The HTTP handlers in the components are split into reader and writer directories. This was a fairly arbitrary distinction, and turns out to not be so helpful. Most read APIs have a corresponding write API, and i would be more natural for them to be in the same file rather than in different directories.

Add device display names

Devices can be given display names, either by adding a initial_device_display_name key on /login and /register requests, or by updating it using the PUT /devices/{deviceId} with a body of {"display_name": ...}.

This requires adding a new column to the device_devices table and new functions to the device storage layer.

Client API checklist

This issue aggregates outstanding client API issues that need to be implemented in order to get dendrite up and running. It does not consider other components, nor does it consider any bugs or additional feature requests.

  • Implement HTTP routing (#11)
  • Implement HTTP request ID tracking (Context package). Related: implementing the logging framework. (#12)
  • Implement stub HTTP endpoints for all CS API paths (only r0?) - Message sending is done now.
  • Glue stub HTTP endpoints through to roomserver code (or equiv)
  • Implement idempotent PUTs (transaction cache)
  • Implement auth checks (extracting tokens from requests and verifying them, NOT room auth checks)
  • Implement version control on HTTP endpoints (assuming we will Get Serious with r0, r1, etc in the future, if not then we might as well not bother with this)
  • Implement extensible registration / login code (given how popular it was to extend Synapse's registration code, we should probably think about this from the get-go)
  • Implement open ID (required for Riot Integrations)

There's also some fairly low-hanging non-room-server fruit which could be tackled:

  • Implement filter APIs (#63)
  • Implement content repo (#59)
  • Implement account data APIs (requires notifications between devices) (#60)
  • Implement push rules APIs (requires notifications between devices)
  • Implement device management APIs (requires notifications between devices and room membership state)

Do we want to think about QoS from the get-go (to make sure we can't end up with disasters like "a random user at 03:54am decides to sync from aeons ago which cripples the server"). This should be tractable in Go if we decide from the start how to do this (e.g. Bundling QoS factors into Context: needs discussion).

Implement the room directory APIs

Implement the /_matrix/client/r0/directory/room/ APIs

In the Client API component:

  • Add the /_matrix/client/r0/directory/room/ HTTP APIs.
  • Store the room alias associations in a postgres database.
  • Add support for join room by alias to /_matrix/client/r0/join HTTP APIs.
  • Write m.room.aliases events for the room if the server is in the room.

Add contexts to everything

We need to go through all the functions that are from API calls and add the go contexts to them.

We should also figure out how to tie requests together on multiple components

Implement the profile APIs

Implement /_matrix/client/r0/profile/... APIs.

In the Client API component:

  • Add the HTTP APIs for getting and setting profile information.
  • Store the profile information in a postgres database.
  • Add the correct profile information to m.room.member events.
  • Work out how we work out which rooms the user is in by consuming the kafka messages produced by the roomserver.
  • Write a m.room.member event with the updated profile information for each room the user is in.

Implement bare bones sync server

https://github.com/matrix-org/dendrite/wiki/Client-Sync-Server-Design

Stages to implement:

  • Generate separate binary and listen on the right paths. Glue in stub HTTP auth. #48
  • Read the roomserver output log (start at Day 0 on startup and replay). #52
  • Remember position in roomserver output log so don't need to start at Day 0. #52
  • Insert each OutputRoomEvent into the database into relevant tables. #54 and #55
  • Implement HTTP long-polling. #56
  • Implement sync token management (returning token to clients, reading from HTTP requests) and return all events as-is (no logic to determine which rooms to return). #57
  • Implement logic to determine which rooms should be returned. Return events in a spec-compliant form. #58 and #69 and #71 and #72

Optimisations have been split out into a separate issue, see #65

Dendrite and Jocko

Is there any reason to think that Dendrite wouldn't work with Travis Jeffery's Go implementation of Kafka, Jocko, instead of Apache Kafka? Jocko is still a young project and is still missing some Kafka features but a single binary version of Kafka with no dependency on ZooKeeper has obvious appeal.

Implement per user account data and room tags.

Implement the /_matrix/client/r0/user/../account_data, /_matrix/client/r0/user/../rooms/../account_data and /_matrix/client/r0/user/..rooms/../tags APIs.

In the Client API component:

  • Add the HTTP APIs.
  • Store the per user data in a postgres database.
  • Write the new account data to an account data kafka topic with the partitions sharded by the user_id the account data belongs to.

In the Sync Server component:

  • Consume the account data kafka topic and wake up sync streams as needed.

Federation: Add query room alias API endpoint

Implement the server portion of: GET /_matrix/federation/v1/query/directory?room_alias=...

See e.g.

func DirectoryRoom(
req *http.Request,
roomAlias string,
federation *gomatrixserverlib.FederationClient,
cfg *config.Dendrite,
aliasAPI api.RoomserverAliasAPI,
) util.JSONResponse {
_, domain, err := gomatrixserverlib.SplitID('#', roomAlias)
if err != nil {
return util.JSONResponse{
Code: 400,
JSON: jsonerror.BadJSON("Room alias must be in the form '#localpart:domain'"),
}
}
var resp gomatrixserverlib.RespDirectory
if domain == cfg.Matrix.ServerName {
queryReq := api.GetAliasRoomIDRequest{Alias: roomAlias}
var queryRes api.GetAliasRoomIDResponse
if err = aliasAPI.GetAliasRoomID(req.Context(), &queryReq, &queryRes); err != nil {
return httputil.LogThenError(req, err)
}
if len(queryRes.RoomID) > 0 {
// TODO: List servers that are aware of this room alias
resp = gomatrixserverlib.RespDirectory{
RoomID: queryRes.RoomID,
Servers: []gomatrixserverlib.ServerName{},
}
} else {
// If the response doesn't contain a non-empty string, return an error
return util.JSONResponse{
Code: 404,
JSON: jsonerror.NotFound("Room alias " + roomAlias + " not found."),
}
}
} else {
resp, err = federation.LookupRoomAlias(req.Context(), domain, roomAlias)
if err != nil {
switch x := err.(type) {
case gomatrix.HTTPError:
if x.Code == 404 {
return util.JSONResponse{
Code: 404,
JSON: jsonerror.NotFound("Room alias not found"),
}
}
}
// TODO: Return 502 if the remote server errored.
// TODO: Return 504 if the remote server timed out.
return httputil.LogThenError(req, err)
}
}
return util.JSONResponse{
Code: 200,
JSON: resp,
}
}
for how to query the room server for directory mappings

Flatbuffer binary encoding?

Once dendrite gets further along, would a flatbuffer binary encoding for the matrix events be considered?

I experimented with encoding the existing json schema as binary using flatbuffers, and it did appear to work alright for m.room.member_example.json. I presume it would be fine for other messages, and that the flatbuffers schemas could be automatically generated with a tool from the matrix-spec repo (Meaning potentially no need to maintain a separate binary protocol specification). I published my test here - https://github.com/matrix-hacks/matrix-flatbuffers

... I believe this would allow clients and servers to completely ditch the need for json parsing/encoding which relieves clients of a lot of their code size and complexity, while increasing message handling performance significantly -- all relevant for minimal/embedded clients written in C and other low level languages.

Flatbuffers already has language bindings for Go (and python and C++ and many others).

Federation: Implement Query profile API

Implement the server portion of: GET /_matrix/federation/v1/query/profile?user_id=...&field=...

See e.g.

func GetDisplayName(
req *http.Request, accountDB *accounts.Database, userID string,
) util.JSONResponse {
localpart, _, err := gomatrixserverlib.SplitID('@', userID)
if err != nil {
return httputil.LogThenError(req, err)
}
profile, err := accountDB.GetProfileByLocalpart(req.Context(), localpart)
if err != nil {
return httputil.LogThenError(req, err)
}
res := displayName{
DisplayName: profile.DisplayName,
}
return util.JSONResponse{
Code: 200,
JSON: res,
}
}
for how to query the room server for profile info

Combined logs

Given a request is going to cause work on multiple components, we should look into how we want to do logging. Specifically whether we want to stuff them all into a single "stream", rather than having each instance logging to a separate file.

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.