diff --git a/.github/workflows/specs.yml b/.github/workflows/specs.yml index 9e4814b..fc15db3 100644 --- a/.github/workflows/specs.yml +++ b/.github/workflows/specs.yml @@ -19,6 +19,15 @@ jobs: # pinned in Gemfile.lock (BUNDLED WITH). bundler-cache: true + - name: Set up Node + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: npm + + - name: Install the Castle browser SDK + run: npm ci + - name: Set up the test database run: | cp config/database.yml.example config/database.yml diff --git a/Dockerfile b/Dockerfile index ece6878..7b73e9b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,11 @@ # syntax=docker/dockerfile:1 +# Fetch the Castle browser SDK from npm (served at runtime from node_modules). +FROM node:20-slim AS frontend +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci + # ---------- Build stage ---------- FROM ruby:3.4.9-slim AS build @@ -21,6 +27,7 @@ RUN gem install bundler -v "${BUNDLER_VERSION}" && \ rm -rf "${BUNDLE_PATH}"/ruby/*/cache COPY . . +COPY --from=frontend /app/node_modules/@castleio/castle-js/dist ./node_modules/@castleio/castle-js/dist # The real database.yml is environment-specific and git-ignored; derive it from # the committed example so the build is reproducible from a clean checkout. diff --git a/README.md b/README.md index 068ce87..598823f 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,13 @@ cd castle-ruby-example bundle install ``` +The Castle browser SDK is served at runtime straight from `node_modules`, so +install it too: + +```bash +npm install +``` + Configure your environment and database: ```bash diff --git a/app/controllers/vendor/castle_js_controller.rb b/app/controllers/vendor/castle_js_controller.rb new file mode 100644 index 0000000..03384dd --- /dev/null +++ b/app/controllers/vendor/castle_js_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Vendor + # Serves the Castle browser SDK from the npm install (node_modules). + class CastleJsController < ActionController::Base + DIST = Rails.root.join('node_modules/@castleio/castle-js/dist') + + skip_forgery_protection + + def show + path = resolved_file + return head :not_found unless path + + send_file path, type: 'application/javascript', disposition: 'inline' + end + + private + + def resolved_file + root = DIST.expand_path + candidate = root.join(params[:filename].to_s).expand_path + return unless candidate.to_s.start_with?("#{root}#{File::SEPARATOR}") + return unless candidate.file? + + candidate + end + end +end diff --git a/app/views/layouts/_castle_js.html.erb b/app/views/layouts/_castle_js.html.erb index 99bcc46..f937508 100644 --- a/app/views/layouts/_castle_js.html.erb +++ b/app/views/layouts/_castle_js.html.erb @@ -1,7 +1,7 @@ <% if ENV["CASTLE_PK"].present? %> <%# Castle browser SDK. Mints the request token that ties the browser to the %> <%# server-side risk/filter calls. See app/assets/javascripts/castle.js. %> - +