GithubHelp home page GithubHelp logo

ttlabs / evaporatejs Goto Github PK

View Code? Open in Web Editor NEW
1.8K 1.8K 206.0 1.35 MB

Javascript library for browser to S3 multipart resumable uploads

JavaScript 82.26% Java 2.47% C# 0.44% HTML 11.18% Python 0.95% Ruby 0.62% Go 0.63% CSS 0.61% PHP 0.84%

evaporatejs's People

Contributors

andrao avatar andrewmnlv avatar bikeath1337 avatar brycefisher avatar caruccio avatar cnorthwood avatar cvn avatar dangerous avatar dconnolly avatar fd avatar fkjaekel avatar gottfrois avatar hugocrd avatar jakubzitny avatar jlines avatar kberryman avatar marogian avatar mattmoreira avatar mcfedr avatar mojodna avatar potomak avatar sebcreme avatar slorber avatar srolija avatar tarang avatar theopolisme avatar tomsaffell avatar uqee avatar wesleykapow avatar xli 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

evaporatejs's Issues

Java Signing Example

I'm starting to use EvaporateJS, great library!

I'd like to contribute with a Java signing example:

import java.io.IOException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

@WebServlet("/signAuth")
public class SigningExample extends HttpServlet
{
    private static final long   serialVersionUID    = 1L;

    private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        String data = request.getParameter("to_sign");

        if(StringUtils.isEmpty(data))
        {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid data: 'to_sign' parameter not informed");
        }
        else
        {
            response.getWriter().write(calculateRFC2104HMAC(data, "<<YOUR_AWS_SECRET_KEY>>"));
        }
    }

    /**
     * http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/HMACAuth.html#AuthJavaSampleHMACSignature
     * 
     * Computes RFC 2104-compliant HMAC signature.
     * 
     * @param data
     *           The data to be signed.
     * @param key
     *           The signing key.
     * @return The Base64-encoded RFC 2104-compliant HMAC signature.
     * @throws ServletException
     * @throws java.security.SignatureException
     *            when signature generation fails
     */
    public static String calculateRFC2104HMAC(String data, String key) throws ServletException
    {
        String result;
        try
        {
            // get an hmac_sha1 key from the raw key bytes
            SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);

            // get an hmac_sha1 Mac instance and initialize with the signing key
            Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
            mac.init(signingKey);

            // compute the hmac on input data bytes
            byte[] rawHmac = mac.doFinal(data.getBytes());

            // base64-encode the hmac
            result = new String(Base64.encodeBase64(rawHmac));
        }
        catch(Exception e)
        {
            throw new ServletException("Failed to generate HMAC: " + e.getMessage());
        }
        return result;
    }
}

Add a timeUrl parameter

I recently run into some RequestTimeTooSkewed errors because of daylight saving time: Example:

My computer: new Date() output is  Thu Nov 06 2014 13:49:36 GMT-0200 (Horário brasileiro de verão) // works fine
Client computer: new Date() output is Thu Nov 06 2014 13:47:15 GMT-0300 (Hora oficial do Brasil) // RequestTimeTooSkewed error

Because of that new Date().toUTCString() gives different values.

I solved this by getting the time from my server, which has NTP installed, so I changed the setupRequest function:

           if(!con.timeUrl)
           {
              requester.dateString = new Date().toUTCString();
           }
           else
           {
              requester.dateString = $.ajax({
                  type: "GET",
                  url: con.timeUrl,
                  cache: false,
                  async: false
              }).responseText;
           }

Server returns the date formatted in the RFC 1123 pattern, same output from JavaScript new Date().toUTCString().

I'd like to suggest this idea to be merged in the official code.

Thanks

Direct upload to Amazon s3: corrupted files

Hello everyone !

I'm currently trying to setup a direct upload from angular-file-upload to Amazon S3 bucket.
The upload process works fine, ex: If I upload an image of 3MB, the image is correctly transferred on my Amazon S3 bucket (located in Francfort btw, using the new AWS V4 signature)

The issue is the following one: The uploaded files cannot be opened and when I try to "cat" to see the binary content of the file, there are 3 lines added:

------WebKitFormBoundaryfOtaE6TQBF0hzBnw
Content-Disposition: form-data; name="file"; filename="01_-_Rock_Or_Bust.mp3"
Content-Type: audio/mp3

For each uploaded file, there is this "webkitformboundary" stuff added inside my file's binary content which is the reason of my issue I guess.

Does anyone know how to solve it?

I have a Node.js server that is basically generating pre-signed url and my app using this plugin to do the upload to S3.

I've been looking to solve this with my team for 24 hours now but no clue at moment.
Any ideas?

Best regards,

Uncaught InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED

The url signing endpoint that I am using requires Authentication, therefore I need to configure evaporate.add calls with the signHeaders option which causes the error in the subject.

signHeaders: {
Authorization:'Bearer ' + Authentication.token
},

I was able to alter the evaporate library to set the headers after the call to xhr.open which solved the issue. Perhaps I can submit a pull request

Cannot retrieve ETag headers while uploading files from Android stock browser

( in upload.on200)
var eTag = xhr.getResponseHeader('ETag')
This returns null when running in Android 4.x versions until version 4.4 on StockBroswer even though I see in Fiddler that eTag is returned.
This seems to be a bug in Android implementation - https://bugs.webkit.org/show_bug.cgi?id=41210
(Cross Origin XMLHttpRequest can not expose headers indicated in Access-Control-Expose-Headers HTTP Response Header)
Works on Stock browser in versions 4.4+

undocumented callbacks?

The progress and complete callbacks appear in the .add documentation, but it looks like there are more than that - cancelled, info, warn, and error:

https://github.com/TTLabs/EvaporateJS/blob/master/evaporate.js#L129-L134

Are these ok to use? If so, can we get these added to the docs?

As I read it:

  • cancelled() gets called when a successful cancel is called for an upload id.
  • info(msg) gets called with a debug/info message, usually logged as well.
  • warn(msg) gets called on a potentially recoverable error, and will be retried (e.g. part upload).
  • error(msg) gets called on an irrecoverable error.

Are those about right?

Browser Support

Map out browsers that EvaporateJS actually work well on so it is more clear where it works and where it does not.

angular module

I have started working on an angular module to make using evaporate even easier.

https://github.com/sourcec0de/ng-evaporate

This is just my proof of concept but now that this is finished I will implement the directive so you can just include the HTML and drop it into your angular project for file upload awesomeness.

Use official aws-sdk.

https://github.com/aws/aws-sdk-js has existed for a few months at least, and includes official implementations of things like uploadPart, completeMultipartUpload, etc. It's probably a good idea to use those officially maintained and tested methods.

Put request forbidden

In my app I send the POST request, successfully https://s3.amazonaws.com/linguakzweb/1d5jq34iclihx1ij7gni.webm?uploads . Then PUT request invoked, the response says that I have wrong signature while the POST request was successful. PUT https://s3.amazonaws.com/linguakzweb/dxqyi6jk216p...Z9_qKTHllKVFO3u03mB_nCNTku6RpDOqKyGKp8qx85eY5w--.
Log says - ### undefined.

Response:

SignatureDoesNotMatchThe request signature we calculated does not match
the signature you provided. Check your key and signing method.AKIAIWG6FO4WCPJ3Z5MQ
PUT

text/plain; charset=UTF-8

x-amz-acl:public-read
x-amz-date:Tue, 18 Aug 2015 14:07:31 GMT
/linguakzweb/164mc2sl97jvgah3jji.webm?partNumber=1&uploadId=fjoMSLJgmYhRnPjuZF4ql5jTOGAgZgHQLekx
.f1gxn05uoUo_VOjEuthU814VMGBPOI.Nn0rvbdKGy4fNfmdLQ--d1JrURO9ezOUHCDrhSH0fP424dI
=50 55 54 0a 0a 74 65 78 74 2f 70 6c 61 69 6e 3b 20 63 68 61 72
73 65 74 3d 55 54 46 2d 38 0a 0a 78 2d 61 6d 7a 2d 61 63 6c 3a 70 75 62 6c 69 63 2d 72 65 61 64 0a 78
2d 61 6d 7a 2d 64 61 74 65 3a 54 75 65 2c 20 31 38 20 41 75 67 20 32 30 31 35 20 31 34 3a 30 37 3a 33
31 20 47 4d 54 0a 2f 6c 69 6e 67 75 61 6b 7a 77 65 62 2f 31 36 34 6d 63 32 73 6c 39 37 6a 76 67 61 68
33 6a 6a 69 2e 77 65 62 6d 3f 70 61 72 74 4e 75 6d 62 65 72 3d 31 26 75 70 6c 6f 61 64 49 64 3d 66 6a
6f 4d 53 4c 4a 67 6d 59 68 52 6e 50 6a 75 5a 46 34 71 6c 35 6a 54 4f 47 41 67 5a 67 48 51 4c 65 6b 78
2e 66 31 67 78 6e 30 35 75 6f 55 6f 5f 56 4f 6a 45 75 74 68 55 38 31 34 56 4d 47 42 50 4f 49 2e 4e 6e
30 72 76 62 64 4b 47 79 34 66 4e 66 6d 64 4c 51 2d 2d5DE43823DC3E540A
9tqPAQ/xY0trw5J1IKX4E5iFpWKMJmimXEmDI3HaJHOyFgvgrJ1VQlgA/MqkVU4W

Please help me.

POST vs PUT; Sample CORS Policy

Clarify the usage of PUT vs POST for adding a new part to S3, provide a sample CORS policy that works in development and provide implementations that might make more sense for production (e.g. embedding an appropriate policy in the returned signature).

Error onfailedauth for step initiate

When trying to run the example, I get "Error onfailedauth for step initiate". What do I need to provide in order to track this error down?

Here's what I changed in evaporate_example.html:

     var _e_ = new Evaporate({
        signerUrl: 'http://localhost:8080/sign_auth',
        aws_key: 'AKIAJVBW466OXU6WHYLQ',
        bucket: 'infogen2-storage',
     });

... and I added an error handler function so I could see what was going wrong. Also I set my secret key in signing_example.py.

thanks for any help you can provide.

Change config on the fly

I was just wondering if there is any way that I can change the configuration for each file. I have an upload service that has a unique signerUrl and AWS public key per upload. Is this possible without any major overhauls?

Thanks for the great software!

Signature issue for SSE-C

I am uploading file using Server-Side Encryption with Customer-Provided Encryption Keys (SSE-C), but I got SignatureDoesNotMatch error.

actual request:

x-amz-acl:private
x-amz-date:Wed, 08 Oct 2014 15:44:52 GMT
x-amz-server-side-encryption-customer-algorithm:AES256
x-amz-server-side-encryption-customer-key:***
x-amz-server-side-encryption-customer-key-MD5:***

AWS calculated:

x-amz-acl:private
x-amz-date:Wed, 08 Oct 2014 15:44:52 GMT
x-amz-server-side-encryption-customer-algorithm:AES256
x-amz-server-side-encryption-customer-key:***
x-amz-server-side-encryption-customer-key-md5:***

the different is MD5 be changed to md5.

I suspect the issue is in
function makeStringToSign(request){

        var x_amz_headers = '', to_sign, header_key_array = [];

        for (var key in request.x_amz_headers) {
            if (request.x_amz_headers.hasOwnProperty(key)) {
                header_key_array.push(key);
            }
        }
        header_key_array.sort();

        header_key_array.forEach(function(header_key,i){
            x_amz_headers += (header_key + ':'+ request.x_amz_headers[header_key] + '\n');
        });


        to_sign = request.method+'\n'+
            '\n'+
            (request.contentType || '')+'\n'+
            '\n'+
            x_amz_headers +
            request.path;
        return encodeURIComponent(to_sign);
    }
}

any comments?

Question: Presigned URLs and Encryption Support

I am evaluating your Evaporate for uploading documents to s3. I have got the basic example working but I have a couple of questions. First, is your auth_sign method same as pre-signed s3 urls, if not how can I add its support. Second, is there a way to add encryption to the upload function or should I use server side encryption?

Thanks.

log is not defined

Uncaught ReferenceError: log is not defined evaporatejs.js:1
(anonymous function) evaporatejs.js:1

version 0.0.2
In the head I have

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"> <script src="evaporatejs.js"> Files still get uploaded, so not serious. Vinny

pubish to npm

Would love to be able to install this from npm. Would you mind adding a package.json and publishing to npm?

Query

The comments mention calculating MD5 and sending them to server and sending ETags of each part to server on part complete to provide support for resumability across different sessions. Can you please elaborate a bit more on how do you plan to use MD5 and ETags to achieve this? I am planning to implement it.

Dropzone to upload

I am wondering if there is any way to enable dropzone for the upload component, so that instead of clicking to upload, people can drag the file to the upload zone to start upload the component. It would be helpful to have the dropzone setting as jquery file upload. Thanks.

Support empty files

I have a costumer that has a database update process where there is a set of SQL scripts, named in sequence. He needs to upload all files, even if some are empty to don't break the sequence.

Evaporate by default fails when the file is empty because no part is uploaded, causing the complete upload request to be malformed:

<Error><Code>MalformedXML</Code><Message>The XML you provided was not well-formed or did not validate against our published schema</Message><RequestId>E00B21BDF42C8090</RequestId><HostId>Pv/R73AHwE/9KN8mNv+hihZGB3AkmVJXM9GlMvG1SG68FZ1a62DsitoVUxbK81rt</HostId></Error>

I made some minor changes to have at least one part upload and I created a isEmpty flag on the part to respect the current empty blob etag md5 check.

Can I create a pull request for you to evaluate the possibility of merging it on master?

Thanks

onError function

Incase something break, the website could notify the user and take other required action.

Speedtest API

It should be possible to do a speed test by profiling how long it takes to upload each part. This used in conjunction with the total file size can inform the user of time remaining for each upload.

Cannot retrieve ETag headers while uploading files

I cannot seem to retrieve the ETag from the multipart upload response headers. I can successfully upload each chunk of the file but when it comes time to complete the multipart upload I am seeing this error:

<Error><Code>InvalidPart</Code><Message>One or more of the specified parts could not be found.  The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.</Message><UploadId>cawD5yowF97n6lErE88PcRtNfz8qeZIcoSLuWk77yPVq0.SgpbcRT_ShZjGnFFB61Vyq.caQGCPx28bSR_CaEA--</UploadId><ETag>null</ETag><RequestId>A8DB1B2F4F3F3C34</RequestId><HostId>/hQ8RMrnOYwtxWrOFD1vvNixxUAVmmQOqjXSPMsjDQ7qcBuQWBggINpa4IPGRagX</HostId><PartNumber>1</PartNumber></Error>

This is the error I see when I am uploading each chunk

image

I am running the application using the example python script provided and have only added my bucket and AWS key.

RequestTimeTooSkewed Error

Around Daylight Savings Time shift we started receiving issues with uploads failing silently. The underlying issue was AWS was responding to uploads with: RequestTimeTooSkewed - The difference between the request time and the current time is too large.

The date is using new Date().toUTCString(); which can be incorrect if the client's time is incorrect. I believe this should come from the server and not the client.

If I submit a pull request to fall back on the server time (received when signing the request) would this be accepted?

Also, I could be missing another way to handle this. Is there one?

pause and resume

Hi,
Any ETA on the pause and resume functions?

Thanks,
Julien

Batch generate signatures for all parts

In order to minimize cross-talk, EvaporateJS should batch-generate signatures. This avoids hitting the application server whenever a new part can be uploaded so there is less time wasted between parts.

Modifications can go into makeParts().

Server should examine request before signing

This was brought up in another issue.

Basically, in most cases the signing request should be checked server-side to make sure the user should have access to the place they upload to. It would be nice if this kind of check were included in the example. Blindly signing will result in a malicious user being able to overwrite every file in the bucket.

Best way to control the S3 object key?

It seems the client can completely control what key an object gets when uploaded to S3. If you wanted to enforce a particular key from the server, what are the best practices?

When the signing URL is called, I could technically extract the object key from the to_sign data, but I was wondering if there was a more elegant approach.

Can’t install via Bower

I saw that there was a bower.json file in the repository, but couldn’t install via Bower using the short name (EvaporateJS). Even searching for just “evaporate” (bower search evaporate) yielded no results.

I can work around this by specifying the GitHub username and repository name (bower install TTLabs/EvaporateJS) but would be good if this package could be published to Bower so I can install using the short name.

String used for Signature Generation does match that which Amazon uses.

We have an application that works great with Evaporate during browser testing. Unfortunately when running within a Capybara-Webkit session it does not.

The string that is signed for the initiate request contains no content-type:

POST\n\n\n\nx-amz-date:Tue, 06 Jan 2015 11:33:18 GMT\n/new-distrify-test-uploads/1%2Fblack.mp4?uploads

Whereas the one in the error returned by Amazon does:

POST\n\napplication/x-www-form-urlencoded\n\nx-amz-date:Tue, 06 Jan 2015 11:33:18 GMT\n/new-distrify-test-uploads/1%2Fblack.mp4?uploads

I am not sure why this is the case. The content-type does not seem to be getting explicitly set on the initiate request. Perhaps it always should be, and the signature generated with this?

(Optional) Session Persistence

Store the file in local storage before uploading, and store relevant metadata (parts uploaded, etc) with the file, so if the browser had to relaunch, the upload session can continue.

This is probably not practical for video files or other kinds of very large files.

Use existing etags in new session

Let's say a file is 60% done, and the upload irrecoverably fails. Then the user tries to upload again a few hours later (not necessarily from the same browser or even machine). We could 'resume' the upload starting at 61%, if we have the etags for the first 60%.

  1. as etags of completed parts are received, POST to an application server (probably in batches, to guarantee order)
  2. when a file is passed to evaporate, check with application server whether some etags already exist for the file (file would need some 'unique' id, which would probably have to be name+lengthInBytes). Maybe could be hash of file, but not if it requires entire file being loaded into memory :)
  3. if existing etags are available then use those, rather than uploading the corresponding part

Need to check how long the uncompiled parts are kept by AWS.

Request denied when using temporary security credentials (IAM roles) and server side has multiple ec2 instances

As temporary security credentials (IAM roles) will have different access key id and secure token on different EC2 machine, current implementation will get 403 (Forbidden) when the first request get security-token and a following request to sign the request params hits different ec2 instance.
These seems need to be setup in one request to server to get all params (including anything need to be signed) and signed signature.

Issues when uploading files with exotic characters in Firefox

I am trying to upload a file with the name "Scree-n Shot 2014-01[]' Ѯ Ч Л `-ê â î û-22 at 5.27.45 PM (1) (2) (1).png" and Firefox always fails the signature verification from S3. Chrome and Safari work great, so I am nervous to say this is a definite bug. Have you seen this issue yourself before?

Tag for 0.0.2

I'd like to pull in EvaporateJS into a Rails project using https://rails-assets.org/. However to do this the repository needs to have semver-compliant git tags.

Would it be possible to create a tag for the 0.0.2 release in this Github repo?

Uncaught InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.

I've got an error (latest Chrome, Firefox, Opera):

evaporate.js:167 processQueue length: 1
evaporate.js:194 starting FileUpload 0
evaporate.js:153 onFileUploadStatusChange
evaporate.js:167 processQueue length: 1
evaporate.js:578 setupRequest() Object {method: "POST", path: "/BUCKETNAME/test_585987047?uploads", step: "initiate", x_amz_headers: Object, not_signed_headers: Object…}
evaporate.js:659 authorizedSend() initiate
evaporate.js:680 Uncaught InvalidStateError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.authorizedSend @ evaporate.js:680initiateUpload @ evaporate.js:264me.start @ evaporate.js:197processQueue @ evaporate.js:182

Detect stalled parts

Looking at our logs, we see that occassionally parts get stuck in status == 2, and never do anything else. This causes failed uploads. I plan to implement a fix to this, which will essentially be:

on a periodic basis, check the byteloaded of each part
if the bytesloaded havent progressed then kill the part, and set back to 'pending'

If anyone has any thoughts / suggestions / comments then please chip in now, so I can include them when I code it up.

The request signature we calculated does not match the signature you provided. Check your key and signing method.

I've got a signer endpoint that creates an hmac using SHA + the access key to our aws account,

here's the add call

tmpOption = {
          name: 'test_' + Math.floor(1000000000*Math.random()),
          file: files[i],
          notSignedHeadersAtInitiate: {
            'Cache-Control': 'max-age=3600'
          },
          // xAmzHeadersAtInitiate : {
          //    'x-amz-acl': 'public-read'
          // },
          complete: function(){
             console.log('complete................yay!');
          },
          progress: function(progress){
             console.log('making progress: ' + progress);
          }
        };
        uploadOptions.push(tmpOption)
        var result = evaporator.add(tmpOption);
}

amazon returns the error:

The request signature we calculated does not match the signature you provided. Check your key and signing method.

What gives?

Mime types have "+" replaced with " " when sent to sign

When signature is generated and the content type is posted to the method, any + in the content type are converted to spaces. Obviously this is because of the use of escape(). The problem is that means the signature is incorrect when it is generated. This could be handled in the server side code but wondering if there was a better fix?

My example is application/epub+zip which is for a ".epub" file.

Support for AWS4 signature (required when using "eu-central-1")

Is there support for using the new AWS4 signature in EvaporateJS? This is required when connecting to buckets which are located in Frankfurt (eu-central-1).

I think the signing requires SHA256 instead of SHA1, but changing my signing-handler to use SHA256 only results in a fail-message:
"failed to get authorization (readyState=4) for initiate. xhr.status: 200. xhr.response: <...>"

Is it only the singing-handler which needs to be fixed? I found some thing here which might be interesting:
http://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html

Any ideas?

Support programmatic pausing and resuming

EvaporateJS should support cancellation or expose a way to cancel things easily. Each upload can be represented as an individual object which exposes observable progress, and allows cancellation, pausing, and resumption.

Update: Cancellation seems already supported.

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.