GithubHelp home page GithubHelp logo

readium / r2-streamer-js Goto Github PK

View Code? Open in Web Editor NEW
21.0 14.0 9.0 4.35 MB

NodeJS Readium2 "streamer"

License: BSD 3-Clause "New" or "Revised" License

TypeScript 42.88% HTML 1.07% JavaScript 55.76% CSS 0.20% Procfile 0.03% Dockerfile 0.05%
readium epub ebooks readium-2 nodejs typescript javascript opds

r2-streamer-js's People

Stargazers

 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

r2-streamer-js's Issues

TypeError with publications in Readium 1

FYI Readium 1 is failing with all 3 publications on demo server.

Example url

Console error:

  • TypeError: undefined is not an object (evaluating 'webpubJson.metadata.publisher[0]') → getMetadata (package_document_parser.js:41)

So that’s

// getElemText(metadataElem, "publisher");
metadata.publisher = webpubJson.metadata.publisher[0].name;

LCP Integration

Hello @danielweck, I am a bit confused regarding how this can be integrated with LCP Server stack.

I noticed that encryption.md talks about LSD Server as being interesting to have, thus it is not supported, right?

That being the case, there is a demo for a protected epub with LCP passphrase "dan", does that mean that I would need to serve one protected epub per user with the LCP passphrase built-in?

If so, how could I achieve that for testing? I tried serving the encrypted folder from LCP Server, but from my understanding there is no passphrase associated with that epub at all.

Broken deployed app on Now.sh (works in Heroku)

(node:80) UnhandledPromiseRejectionWarning: TypeError: obj.hasOwnProperty is not a function
    at Array.forEach (<anonymous>)
    at Object.keys.forEach (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:32:21)
    at traverseJsonObjects_ (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:31:26)
    at Object.keys.forEach (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:35:21)
    at Array.forEach (<anonymous>)
    at traverseJsonObjects_ (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:31:26)
    at Object.keys.forEach (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:35:21)
    at Array.forEach (<anonymous>)
    at traverseJsonObjects_ (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:31:26)
    at Object.keys.forEach (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:35:21)
    at Array.forEach (<anonymous>)
    at traverseJsonObjects_ (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:31:26)
    at traverseJsonObjects_ (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:26:17)
    at Object.keys.forEach (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:35:21)
    at Array.forEach (<anonymous>)
    at traverseJsonObjects_ (/home/nowuser/src/node_modules/r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils.js:31:26)
(node:80) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 3)

Configurable features (not just API/constructor parameters, maybe config file?)

For example, to disable the built-in sample "readers" (NYPL demo, etc.), HTTP CORS (in some cases they are in fact unwanted), OPDS micro-services, etc. etc.
(there are many optional features, perhaps the server constructor should launch by default with no HTTP route activated, and a convenience method on the server class could implement a baseline suitable for "readium desktop", another for the Heroku/Now.sh online deployment, etc.)

R1 - R2 mashup stuck in subsequent hit

Noticed strange behavior when using R1 reader with Node JS R2 streamer.
It looks like the subsequent hit of the same URL results in WebPub manifest
being served from the cache is not correct.

Here is the environment:

First hit results in the book being open OK. Subsequent hit or refresh results in
load indicator displayed indefinitely.

Here are the logs from R2 streamer:

And so on - one hit is a success, subsequent from "cache" failure.

LRU Least Recently Used caching strategy

This "TODO" code comment has been present from day-1 in the TypeScript source, still unresolved (time to file an issue!):

public cachePublication(filePath: string, pub: Publication) {
// TODO: implement LRU caching algorithm? Anything smarter than this will do!
if (!this.isPublicationCached(filePath)) {
this.pathPublicationMap[filePath] = pub;
}
}

Basically, the cache of ReadiumWebPubManifest models grows indefinitely in the current implementation.

Publications in OPDS2 feed always empty

This is how I instantiate the readium server:

import {
    Server
} from "r2-streamer-js";

const OPDSServer = new Server({
    disableDecryption: false, // deactivates the decryption of encrypted resources (Readium LCP).
    disableOPDS: false, // deactivates the HTTP routes for the OPDS "micro services" (browser, converter)
    disableReaders: false, // deactivates the built-in "readers" for ReadiumWebPubManifest (HTTP static host / route).
    disableRemotePubUrl: false, // deactivates the HTTP route for loading a remote publication.
    maxPrefetchLinks: 5, // Link HTTP header, with rel = prefetch, see server.ts MAX_PREFETCH_LINKS (default = 10)
});

const url = async () => await OPDSServer.start(5643, false);
url().then(async (res) => {
    console.log(res);
    const publicationURLs = OPDSServer.addPublications([
        "http://localhost:3000/comics/Iron Man/Iron Man - V1 193.cbz",
    ]);
    console.log(publicationURLs);
    OPDSServer.publicationsOPDS();

    const publication = await OPDSServer.loadOrGetCachedPublication(
        "http://localhost:3000/comics/Iron Man/Iron Man - V1 193.cbz",
    );
    console.log(publication);
    OPDSServer.publicationsOPDS();
});

When I look at the OPDS2 feed located here http://localhost:5643/opds2/publications.json/show, I get this response:

{
    "metadata": {
        "numberOfItems": 0,
        "@type": "http://schema.org/DataFeed",
        "title": "Readium 2 OPDS 2.0 Feed",
        "modified": "Wed Jul 14 2021 09:43:52 GMT-0700 (Pacific Daylight Time)"
    },
    "links": [
        {
            "type": "application/opds+json",
            "rel": "self",
            "href": "http://localhost:5643/opds2/publications.json"
        }
    ],
    "publications": [ ]
}

Why isn't my comic listed in the publications list?

CORS preflight (HTTP `options` method)

Currently, a number of HTTP routes / URL paths defined in r2-streamer-js invoke the setResponseCORS() utility function in order to populate HTTP responses with adequate CORS headers:

public setResponseCORS(res: express.Response) {
res.setHeader("Access-Control-Allow-Origin",
"*");
res.setHeader("Access-Control-Allow-Methods",
"GET, HEAD, OPTIONS"); // POST, DELETE, PUT, PATCH
res.setHeader("Access-Control-Allow-Headers",
"Content-Type, Content-Length, Accept-Ranges, Content-Range, Range, Link, Transfer-Encoding");
res.setHeader("Access-Control-Expose-Headers",
"Content-Type, Content-Length, Accept-Ranges, Content-Range, Range, Link, Transfer-Encoding");
}

( https://github.com/readium/r2-streamer-js/blob/develop/src/http/server.ts#L259-L271 )

For example, for serving the ReadiumWebPublicationManifest (JSON):

server.setResponseCORS(res);
res.set("Content-Type", "application/webpub+json; charset=utf-8");

( https://github.com/readium/r2-streamer-js/blob/develop/src/http/server-manifestjson.ts#L344-L345 )

However, what about HTTP options for CORS? How is this useful? What are the use-cases?

Code example:

public expressOptions(paths: string[], func: express.Handler) {
    this.expressApp.options(paths, func);
}

...added to:

public expressUse(pathf: string, func: express.Handler) {
this.expressApp.use(pathf, func);
}
public expressGet(paths: string[], func: express.Handler) {
this.expressApp.get(paths, func);
}

( https://github.com/readium/r2-streamer-js/blob/develop/src/http/server.ts#L136-L142 )

...and:

_server.expressOptions(["/URL_ROUTE_PATH"],
    (req: express.Request, res: express.Response) => {

    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Credentials", true);
    res.setHeader("Access-Control-Allow-Methods", req.headers["access-control-request-method"]);
    res.setHeader("Access-Control-Allow-Headers", req.headers["access-control-request-headers"]);
    res.status(200).end();
});

Inspired from NYPL's customizations:

https://github.com/NYPL-Simplified/opds-web-client/blob/1600781c267e241e5deb4094007e50df79eb6236/packages/server/index.js#L73
( https://github.com/NYPL-Simplified/opds-web-client/blob/master/packages/server/index.js#L73 )

CC @aslagle

TypeError: Cannot read property 'push' of undefined

I'm getting this error on a few different epubs:

$ yarn run cli /Users/amyslagle/Downloads/677.epub 
yarn run v0.21.3
$ node './dist/src/cli.js' /Users/amyslagle/Downloads/677.epub
process.cwd():
/Users/amyslagle/dev/r2-streamer-js
__dirname:
/Users/amyslagle/dev/r2-streamer-js/dist/src
args:
[ '/Users/amyslagle/Downloads/677.epub' ]
/Users/amyslagle/Downloads/677.epub
== EpubParser: reject
TypeError: Cannot read property 'push' of undefined
    at EpubParser.fillTOCFromNavPoint (/Users/amyslagle/dev/r2-streamer-js/dist/src/parser/epub.js:945:13)
    at ncx.Points.forEach (/Users/amyslagle/dev/r2-streamer-js/dist/src/parser/epub.js:917:22)
    at Array.forEach (native)
    at EpubParser.fillTOCFromNCX (/Users/amyslagle/dev/r2-streamer-js/dist/src/parser/epub.js:916:24)
    at forEach (/Users/amyslagle/dev/r2-streamer-js/dist/src/parser/epub.js:172:34)
    at Array.forEach (native)
    at Promise (/Users/amyslagle/dev/r2-streamer-js/dist/src/parser/epub.js:91:22)
    at EpubParser.createPublicationPromise (/Users/amyslagle/dev/r2-streamer-js/dist/src/parser/epub.js:48:16)
    at Parse.zipPromise.then (/Users/amyslagle/dev/r2-streamer-js/dist/src/parser/epub.js:44:25)
false
✨  Done in 0.48s.

Is there any way to add information to the metadata when generating the manifest file?

I need to migrate old epub files.

When generating the manifest.json file, I would like to add the 'media-overlay' entry in the code below, please let me know if there is a way to do this.

  • as-is
"metadata": {
    "@type": "http://schema.org/Book",
    "title": "Aladdin and the Magic Lamp",
    "identifier": "urn:uuid:978-1-4509-7342-7",
    "author": "retold by Kathryn L. O’Dell",
    "narrator": "Author",
    "publisher": "Benchmark Education Company",
    "language": "en-US",
    "modified": "Tue Apr 07 2015 10:13:00 GMT+0900 (대한민국 표준시)",
    "published": "Tue Feb 10 2015 20:17:22 GMT+0900 (대한민국 표준시)",
    "readingProgression": "ltr",
    "rendition": {
        "layout": "fixed",
        "orientation": "landscape",
        "spread": "auto"
    },
    "rendition:layout": "pre-paginated",
    "rendition:orientation": "landscape",
    "rendition:spread": "auto"
},
  • to-be
"metadata": {
    "@type": "http://schema.org/Book",
    "title": "Aladdin and the Magic Lamp",
    "identifier": "urn:uuid:978-1-4509-7342-7",
    "author": "retold by Kathryn L. O’Dell",
    "narrator": "Author",
    "publisher": "Benchmark Education Company",
    "language": "en-US",
    "modified": "Tue Apr 07 2015 10:13:00 GMT+0900 (대한민국 표준시)",
    "published": "Tue Feb 10 2015 20:17:22 GMT+0900 (대한민국 표준시)",
    "readingProgression": "ltr",
    "media-overlay": { // < --- need to add this
        "active-class": "-epub-media-overlay-active"
    },
    "rendition": {
        "layout": "fixed",
        "orientation": "landscape",
        "spread": "auto"
    },
    "rendition:layout": "pre-paginated",
    "rendition:orientation": "landscape",
    "rendition:spread": "auto"
    },

Collision between keyParams and Path64 in NYPL Reader demo

FYI we just had a bug with this one:

https://github.com/edrlab/r2-streamer-js/blob/b66d92e2c2a29ca31d252661deee9a0edeabc62f/misc/readers/reader-NYPL/index.html#L25-L39

as the streamer created a path64 path containing the "=" character and consequently split the path to the manifest.json.

So instead of

https://website/pub/L3Zhci9qZWxseWJvb2tzL3JlYWRpdW0vYXNzZXRzL0ZhckZyb21UaGVNYWRkaW5nQ3Jvd2QtY292ZXJBLmVwdWI=/manifest.json

it returned

https://website/pub/L3Zhci9qZWxseWJvb2tzL3JlYWRpdW0vYXNzZXRzL0ZhckZyb21UaGVNYWRkaW5nQ3Jvd2QtY292ZXJBLmVwdWI

Can't run as a binary

I'm trying to use this in the webpub-viewer project to replace the in-tree copy.
I added r2-streamer-js as a devDependency and thought I could use it as a binary in one of my project's scripts.

It is not working unfortunately.

> [email protected] streamed ./webpub-viewer
> r2-streamer-js-server ./examples/streamed/epubs/

./webpub-viewer/node_modules/.bin/r2-streamer-js-server: line 1: use strict: command not found
./webpub-viewer/node_modules/.bin/r2-streamer-js-server: line 2: syntax error near unexpected token `exports,'
./webpub-viewer/node_modules/.bin/r2-streamer-js-server: line 2: `Object.defineProperty(exports, "__esModule", { value: true });'

I believe you need to prepend the declared "bin" script with a node "shebang".
Example: https://github.com/isaacs/rimraf/blob/master/bin.js (Line 1)

After manually adding #!/usr/bin/env node to the dist bin script it works as expected.

gitrev.json Unexpected token < in JSON at position 0

https://readium2.herokuapp.com/version/show

SyntaxError: /app/dist/gitrev.json: Unexpected token < in JSON at position 0
    at JSON.parse (<anonymous>)
    at Object.Module._extensions..json (module.js:681:27)
    at Module.load (module.js:575:32)
    at tryModuleLoad (module.js:515:12)
    at Function.Module._load (module.js:507:3)
    at Module.require (module.js:606:17)
    at require (internal/module.js:11:18)
    at Server.expressApp.get (/app/dist/es8-es2017/src/http/server.js:184:29)
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
    at next (/app/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/app/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
    at /app/node_modules/express/lib/router/index.js:281:22
    at param (/app/node_modules/express/lib/router/index.js:354:14)
    at param (/app/node_modules/express/lib/router/index.js:365:14)
    at Function.process_params (/app/node_modules/express/lib/router/index.js:410:3)

https://github.com/edrlab/r2-streamer-js-dist/blob/develop/dist/gitrev.json
and
https://rawgit.com/edrlab/r2-streamer-js-dist/develop/dist/gitrev.json

See:
https://github.com/edrlab/r2-streamer-js/blob/develop/package-scripts.cson#L281

Fetching Media Overlay Spine Item resource fails with CORS

This happens in R1 reader with Node JS R2 streamer testing MO functionality.
R1 reader pointing to R2 Web Pub manifest served by Node JS R2 streamer:
http://localhost:8080/dev/index_RequireJS_no-optimize_LITE.html?epub=http://localhost:3000/pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/manifest.json

When MO data for Spine Item is accessed, fetch is failing with CORS:

Fetch API cannot load
http://localhost:3000/pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/media-overlay?resource=OEBPS%2FText%2Fp022.xhtml. Redirect
from
'http://localhost:3000/pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/media-overlay?resource=OEBPS%2FText%2Fp022.xhtml'
to
'http://localhost:3000/pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/media-overlay/?resource=OEBPS%2FText%2Fp022.xhtml'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is
present on the requested resource. Origin 'http://localhost:8080' is therefore
not allowed access. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.

Indeed Network tab shows redirection from:

Request URL:http://localhost:3000/pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/media-overlay?resource=OEBPS%2FText%2Fp003.xhtml

to:

Location:/pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/media-overlay/?resource=OEBPS%2FText%2Fp003.xhtml

  • R2 Node JS Streamer log shows:

r2:server:main REDIRECT: /pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/media-overlay?resource=OEBPS%2FText%2Fp010.xhtml ==> /pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/media-overlay/?resource=OEBPS%2FText%2Fp010.xhtml +37s
::1 - - [10/Jun/2017:00:52:56 +0000] "GET /pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/media-overlay?resource=OEBPS%2FText%2Fp010.xhtml HTTP/1.1" 301 161 "http://localhost:8080/dev/index_RequireJS_no-optimize_LITE.html?epub=http://localhost:3000/pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/manifest.json" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"

Here is the environment:

  • R1 reader
    cd ~/ep/readium/readium-js-viewer
    npm run http

  • R2 streamer
    cd ~/r2-streamer-js
    yarn run server-debug .

Note, that there is no problem accessing this URL directly:
http://localhost:3000/pub/L1VzZXJzL21pY2hhZWxzL3IyLXN0cmVhbWVyLWpzL1RhbGVzRnJwbVNoZWtzcGlyZS5lcHVi/media-overlay?resource=OEBPS%2FText%2Fp003.xhtml

OPDS2 JSON output from EPUB folder

OPDS browsing view

http://oacontent.librarysimplified.org/

=>

Readium2 streamer basic OPDS viewer:
https://readium2.herokuapp.com/opds/http%3A%2F%2Foacontent.librarysimplified.org%2F

Readium1 "library" view:
https://readium.firebaseapp.com/?epubs=https%3A%2F%2Fcors-anywhere.herokuapp.com%2Fhttp%3A%2F%2Foacontent.librarysimplified.org%2F

NYPL browser (+ source):
https://opds-browser-demo.herokuapp.com/collection/http%3A%2F%2Foacontent.librarysimplified.org/
+
https://github.com/NYPL-Simplified/opds-web-client

TODO: redirect https://readium2.herokuapp.com/opds/{OPDS_URL} to https://opds-browser-demo.herokuapp.com/collection/{OPDS_URL}, and change the "download EPUB" link to https://readium2.herokuapp.com/opds/{EPUB_URL}?

EPUB.js URLs

Can r2-streamer-js generate an OPDS feed?

Like the question says, I have a library of comic books in cbz and cbr files. What is the most trivial way to generate a feed of these publications for consumption by a downstream app such as Panels on iOS?

Unable to use cloudfront signed url in Load HTTP publication URL!

Hi @danielweck ,

Today i need to process epub store in s3 with cloudfront signed url.
But with the same epub I stored in streamer server, i upload it to s3 then the streamer can't parse spine.

  • Streamer console log:
    image

  • Streamer reponse to browser:
    image

Epub uuid: urn:uuid:6f796b4e-0cd5-4ed5-a319-2fe6a9d14e9f

As I said before, the above epub still can parsed if epub url doesn't use cloudfront signed url!

Stored in server:
image

Stored in s3 with signed url:
image

Tuan Anh,
Thanks

Streamer doesn't response encrypted publication resources without browse manifestjson page

Hi @danielweck,

  • Today I has a problem about streamer doesn't response encrypted publication resource(see below images)
    image
    image

I found that if I not browse ./manifest.json/show/all page, i will get error r2:lcp#transform/transformer-lcp LCP not ready!
image

My solution

  • To fix this error, i need to add this code to server-assets.ts like this:
    image

Did i miss something?
I'm very interested in streamer can stream data.

Thank you

Couple of sample mashups do not work

in server route, publication ID is base64-encoded filesystem path (or alternatively URL to public HTTP publication)

It would be great if this could be arbitrary UUID or database URL scheme (etc.) with backend capable of supported various storage retrieval mechanisms. This is somewhat related to the notion of "fetcher" in the Readium2 architecture (e.g. exploded EPUBs vs. packed/zipped publications), but there are deeper ramifications due to how the decoded base64 string is used to map other behaviours. To be investigated further...

[DAISY-AVH] Content-type broken on reading-orders links


> URL=`curl -H "Authorization: Bearer $BEARER" -s https://eole-recette.avh.asso.fr/api/v1/search | jq -r '.publications[0].links[0].href'`
> curl -H "Authorization: Bearer $BEARER"  -s $URL | jq '.readingOrder[0]'
{
  "type": "audio/?",
  "duration": 12.896,
  "href": "Y061664/aud001.mp3?r2tkn=eyJoYXNoIjoiNTRkYWY5MjhhNTZlYzY4OWY5OWI5NmQ0MWRhZDY0MTdiYTBkNjdlNzdmZWE2OWI4NmVmODNlZTBiYTZmNDQ5MiIsInRpbWVzdGFtcCI6MTY2MDc0MDg2MTM3MywiZXhwaXJ5Ijo4NjQwMH0%3D"
}

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.