Documentation

Deployment

Build the bundle, push to a CDN, and roll out updates without breaking embeds.

The widget ships in two flavours and has three build outputs. This page covers what each produces, how cache busting works, and how to host the static bundle on your own CDN.

Build outputs

The repo defines three relevant scripts in package.json:

ScriptOutputWhat it's for
yarn startdev server on localhost:3000Local iteration
yarn buildbuild/ (CRA static SPA + appmint-chat.js)CDN-hosted standalone widget
yarn build:libdist/bundle.js (UMD)Experimental — no consumer ships from this output today

yarn build is the canonical "ship to CDN" path. It runs react-scripts build, then node update-script-hashes.js to copy the hashed CRA output to a stable main.js filename so customers can pin a single URL.

yarn build:lib was intended for an @appmint/chat npm package. The package was never published — chat-client/package.json is private: true with no exports field — so the Rollup output isn't currently consumed anywhere. Treat the lib build as exploratory until the package surface is finalised.

Cache busting

Both bundles use content-hashed filenames. The wrinkle: customers paste the URL into a <script> tag and never update it, so the URL must stay stable while the contents change. update-script-hashes.js solves this by:

  1. Finding the largest non-chunk JS file in build/static/js/ (the main bundle).
  2. Copying it to build/static/js/main.js.
  3. Doing the same for the main CSS file.

So even though CRA writes main.abc123.js, the script also produces main.js for stable embedding. The script tag URL https://web.appmint.space/chat-client/static/js/main.js always resolves to the latest build.

// update-script-hashes.js — find largest non-chunk JS, copy to main.js
const files = fs.readdirSync(jsDir);
const mainJs = files
  .filter(f => f.endsWith('.js') && !f.endsWith('.chunk.js'))
  .sort((a, b) => fs.statSync(path.join(jsDir, b)).size - fs.statSync(path.join(jsDir, a)).size)[0];
fs.copyFileSync(path.join(jsDir, mainJs), path.join(jsDir, 'main.js'));

The bundled chat-demo also ships at build/chat-demo.html — a complete script-tag example you can copy as a starting point.

Hosting on your own CDN

The default deploy target is a DigitalOcean Space (deploy.js in the repo). To self-host:

  1. 1

    Build

    yarn install
    yarn build
    

    Output lands in build/. Confirm build/static/js/main.js exists and matches the hashed file in size — if not, update-script-hashes.js didn't run; check the postbuild chain in package.json.

  2. 2

    Sync to your bucket

    The repo ships a deploy script that pushes build/ to a DigitalOcean Space:

    node deploy.js
    

    It reads credentials from .env.deploy (gitignored) — DO_SPACES_BUCKET, DO_SPACES_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, optional DO_SPACES_PREFIX and DO_CDN_ENDPOINT_ID. For S3, Cloudflare R2, or any other S3-compatible target, the same aws s3 sync invocation works — just point --endpoint-url at the right host.

    Make sure all uploaded objects are publicly readable (--acl public-read or the equivalent bucket policy).

  3. 3

    Replace the script URLs

    In your embed snippet, replace the AppMint CDN host with your own:

    <link rel="stylesheet" href="https://your-cdn.example.com/chat-client/static/css/main.css">
    <script src="https://your-cdn.example.com/chat-client/appmint-chat.js" onload="initializeChat()"></script>
    

    The bundle has no compiled-in CDN URL — only AppEngine endpoints. So self-hosting works without a code change.

  4. 4

    Purge the CDN cache

    If your CDN sits in front of the bucket (Cloudflare, DO CDN, CloudFront), invalidate the paths after each deploy:

    # DigitalOcean
    doctl compute cdn flush <endpoint-id> --files "/*"
    
    # CloudFront
    aws cloudfront create-invalidation --distribution-id <id> --paths "/chat-client/*"
    

    deploy.js does this automatically when DO_CDN_ENDPOINT_ID is set.

Versioning the bundle

There is no published npm package. For the script-tag bundle there's no version pin — every page pulls the current build. To support customers who want to pin, publish versioned URLs alongside main.js:

/chat-client/v0.1.0/static/js/main.js
/chat-client/v0.2.0/static/js/main.js
/chat-client/static/js/main.js          # always latest

The repo doesn't ship this layout out of the box. Add it to deploy.js if you need it.

Pre-deploy checklist

Before pushing a new build:

  • Verify build/index.html and build/chat-demo.html exist.
  • Open chat-demo.html locally with the new bundle and confirm the bubble appears.
  • Smoke-test send/receive against a staging AppEngine to catch socket regressions.
  • If you bumped a major React version or socket.io-client version, run the unit tests in src/locales/__tests__/ and any consumer integration tests.
Don't forget the CSS

The CSS bundle (main.css) is required for the layout. Skipping it leaves the panel unstyled and badly positioned. Always sync both JS and CSS together.

Rollbacks

The simplest rollback is to keep the previous main.js in a versioned folder and swap the stable URL back:

aws s3 cp s3://<bucket>/chat-client/v0.1.0/static/js/main.js \
          s3://<bucket>/chat-client/static/js/main.js \
          --endpoint-url <url> --acl public-read

CDN invalidate, refresh customer pages, done. Because the widget loads its config at runtime, downgrading the bundle won't lose any customer data — only the rendered UI rolls back.