requestjs-rails-0.0.14/0000775000175000017500000000000015144147010013766 5ustar sorensorenrequestjs-rails-0.0.14/requestjs-rails.gemspec0000664000175000017500000000263615144147010020477 0ustar sorensoren######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: requestjs-rails 0.0.14 ruby lib Gem::Specification.new do |s| s.name = "requestjs-rails".freeze s.version = "0.0.14".freeze s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Marcelo Lauxen".freeze] s.date = "2025-12-11" s.email = "marcelolauxen16@gmail.com".freeze s.files = ["MIT-LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "app/assets/javascripts/rails-requestjs.js".freeze, "app/assets/javascripts/requestjs.js".freeze, "config/importmap.rb".freeze, "lib/install/requestjs_with_asset_pipeline.rb".freeze, "lib/install/requestjs_with_webpacker.rb".freeze, "lib/requestjs-rails.rb".freeze, "lib/requestjs/engine.rb".freeze, "lib/requestjs/version.rb".freeze, "lib/tasks/requestjs_tasks.rake".freeze] s.homepage = "https://github.com/marcelolx/requestjs-rails".freeze s.licenses = ["MIT".freeze] s.rubygems_version = "3.6.2".freeze s.summary = "A tiny Fetch API wrapper that allows you to make http requests without need to handle to send the CSRF Token on every request".freeze s.specification_version = 4 s.add_runtime_dependency(%q.freeze, [">= 7.1.0".freeze]) end requestjs-rails-0.0.14/lib/0000775000175000017500000000000015144147010014534 5ustar sorensorenrequestjs-rails-0.0.14/lib/tasks/0000775000175000017500000000000015144147010015661 5ustar sorensorenrequestjs-rails-0.0.14/lib/tasks/requestjs_tasks.rake0000644000175000017500000000157615144147010021766 0ustar sorensorendef run_requestjs_install_template(path) system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/#{path}.rb", __dir__)}" end namespace :requestjs do desc "Install Request.JS into the app" task :install do if defined?(Webpacker::Engine) Rake::Task["requestjs:install:webpacker"].invoke elsif defined?(Importmap) Rake::Task["requestjs:install:asset_pipeline"].invoke else puts "You must either be running Webpacker or importmap-rails to use this gem." end end namespace :install do desc "Install Request.JS on the app with the asset pipeline" task :asset_pipeline do run_requestjs_install_template "requestjs_with_asset_pipeline" end desc "Install Request.JS on the app with webpacker" task :webpacker do run_requestjs_install_template "requestjs_with_webpacker" end end end requestjs-rails-0.0.14/lib/requestjs/0000775000175000017500000000000015144147010016561 5ustar sorensorenrequestjs-rails-0.0.14/lib/requestjs/version.rb0000644000175000017500000000005215144147010020566 0ustar sorensorenmodule Requestjs VERSION = "0.0.14" end requestjs-rails-0.0.14/lib/requestjs/engine.rb0000644000175000017500000000073715144147010020360 0ustar sorensorenmodule Requestjs class Engine < ::Rails::Engine initializer "requestjs.assets" do if Rails.application.config.respond_to?(:assets) Rails.application.config.assets.precompile += %w( rails-requestjs.js requestjs.js ) end end initializer "requestjs.importmap", before: "importmap" do |app| if Rails.application.respond_to?(:importmap) app.config.importmap.paths << Engine.root.join("config/importmap.rb") end end end end requestjs-rails-0.0.14/lib/requestjs-rails.rb0000644000175000017500000000011415144147010020210 0ustar sorensorenmodule Requestjs end require "requestjs/version" require "requestjs/engine"requestjs-rails-0.0.14/lib/install/0000775000175000017500000000000015144147010016202 5ustar sorensorenrequestjs-rails-0.0.14/lib/install/requestjs_with_webpacker.rb0000644000175000017500000000010715144147010023626 0ustar sorensorensay "Installing Request.JS dependency" run "yarn add @rails/request.js"requestjs-rails-0.0.14/lib/install/requestjs_with_asset_pipeline.rb0000644000175000017500000000055215144147010024673 0ustar sorensorenAPP_JS_ROOT = Rails.root.join("app/assets/javascripts") APP_JS_PATH = APP_JS_ROOT.exist? ? APP_JS_ROOT : Rails.root.join("app/javascript") create_file Rails.root.join("app/javascript/application.js") unless APP_JS_PATH.exist? say "Import Request.JS in existing #{APP_JS_PATH}" append_to_file APP_JS_PATH.join("application.js"), %(import "@rails/request.js"\n) requestjs-rails-0.0.14/config/0000775000175000017500000000000015144147010015233 5ustar sorensorenrequestjs-rails-0.0.14/config/importmap.rb0000644000175000017500000000005315144147010017564 0ustar sorensorenpin "@rails/request.js", to: "requestjs.js"requestjs-rails-0.0.14/app/0000775000175000017500000000000015144147010014546 5ustar sorensorenrequestjs-rails-0.0.14/app/assets/0000775000175000017500000000000015144147010016050 5ustar sorensorenrequestjs-rails-0.0.14/app/assets/javascripts/0000775000175000017500000000000015144147010020401 5ustar sorensorenrequestjs-rails-0.0.14/app/assets/javascripts/requestjs.js0000644000175000017500000002134215144147010022764 0ustar sorensorenclass FetchResponse { constructor(response) { this.response = response; } get statusCode() { return this.response.status; } get redirected() { return this.response.redirected; } get ok() { return this.response.ok; } get unauthenticated() { return this.statusCode === 401; } get unprocessableEntity() { return this.statusCode === 422; } get authenticationURL() { return this.response.headers.get("WWW-Authenticate"); } get contentType() { const contentType = this.response.headers.get("Content-Type") || ""; return contentType.replace(/;.*$/, ""); } get headers() { return this.response.headers; } get html() { if (this.contentType.match(/^(application|text)\/(html|xhtml\+xml)$/)) { return this.text; } return Promise.reject(new Error(`Expected an HTML response but got "${this.contentType}" instead`)); } get json() { if (this.contentType.match(/^application\/.*json$/)) { return this.responseJson || (this.responseJson = this.response.json()); } return Promise.reject(new Error(`Expected a JSON response but got "${this.contentType}" instead`)); } get text() { return this.responseText || (this.responseText = this.response.text()); } get isTurboStream() { return this.contentType.match(/^text\/vnd\.turbo-stream\.html/); } get isScript() { return this.contentType.match(/\b(?:java|ecma)script\b/); } async renderTurboStream() { if (this.isTurboStream) { if (window.Turbo) { await window.Turbo.renderStreamMessage(await this.text); } else { console.warn("You must set `window.Turbo = Turbo` to automatically process Turbo Stream events with request.js"); } } else { return Promise.reject(new Error(`Expected a Turbo Stream response but got "${this.contentType}" instead`)); } } async activeScript() { if (this.isScript) { const script = document.createElement("script"); const metaTag = document.querySelector("meta[name=csp-nonce]"); if (metaTag) { const nonce = metaTag.nonce === "" ? metaTag.content : metaTag.nonce; if (nonce) { script.setAttribute("nonce", nonce); } } script.innerHTML = await this.text; document.body.appendChild(script); } else { return Promise.reject(new Error(`Expected a Script response but got "${this.contentType}" instead`)); } } } class RequestInterceptor { static register(interceptor) { this.interceptor = interceptor; } static get() { return this.interceptor; } static reset() { this.interceptor = undefined; } } function getCookie(name) { const cookies = document.cookie ? document.cookie.split("; ") : []; const prefix = `${encodeURIComponent(name)}=`; const cookie = cookies.find(cookie => cookie.startsWith(prefix)); if (cookie) { const value = cookie.split("=").slice(1).join("="); if (value) { return decodeURIComponent(value); } } } function compact(object) { const result = {}; for (const key in object) { const value = object[key]; if (value !== undefined) { result[key] = value; } } return result; } function metaContent(name) { const element = document.head.querySelector(`meta[name="${name}"]`); return element && element.content; } function stringEntriesFromFormData(formData) { return [ ...formData ].reduce((entries, [name, value]) => entries.concat(typeof value === "string" ? [ [ name, value ] ] : []), []); } function mergeEntries(searchParams, entries) { for (const [name, value] of entries) { if (value instanceof window.File) continue; if (searchParams.has(name) && !name.includes("[]")) { searchParams.delete(name); searchParams.set(name, value); } else { searchParams.append(name, value); } } } class FetchRequest { constructor(method, url, options = {}) { this.method = method; this.options = options; this.originalUrl = url.toString(); } async perform() { try { const requestInterceptor = RequestInterceptor.get(); if (requestInterceptor) { await requestInterceptor(this); } } catch (error) { console.error(error); } const fetch = window.Turbo ? window.Turbo.fetch : window.fetch; const response = new FetchResponse(await fetch(this.url, this.fetchOptions)); if (response.unauthenticated && response.authenticationURL) { return Promise.reject(window.location.href = response.authenticationURL); } if (response.isScript) { await response.activeScript(); } const responseStatusIsTurboStreamable = response.ok || response.unprocessableEntity; if (responseStatusIsTurboStreamable && response.isTurboStream) { await response.renderTurboStream(); } return response; } addHeader(key, value) { const headers = this.additionalHeaders; headers[key] = value; this.options.headers = headers; } sameHostname() { if (!this.originalUrl.startsWith("http:") && !this.originalUrl.startsWith("https:")) { return true; } try { return new URL(this.originalUrl).hostname === window.location.hostname; } catch (_) { return true; } } get fetchOptions() { return { method: this.method.toUpperCase(), headers: this.headers, body: this.formattedBody, signal: this.signal, credentials: this.credentials, redirect: this.redirect, keepalive: this.keepalive }; } get headers() { const baseHeaders = { "X-Requested-With": "XMLHttpRequest", "Content-Type": this.contentType, Accept: this.accept }; if (this.sameHostname()) { baseHeaders["X-CSRF-Token"] = this.csrfToken; } return compact(Object.assign(baseHeaders, this.additionalHeaders)); } get csrfToken() { return getCookie(metaContent("csrf-param")) || metaContent("csrf-token"); } get contentType() { if (this.options.contentType) { return this.options.contentType; } else if (this.body == null || this.body instanceof window.FormData) { return undefined; } else if (this.body instanceof window.File) { return this.body.type; } return "application/json"; } get accept() { switch (this.responseKind) { case "html": return "text/html, application/xhtml+xml"; case "turbo-stream": return "text/vnd.turbo-stream.html, text/html, application/xhtml+xml"; case "json": return "application/json, application/vnd.api+json"; case "script": return "text/javascript, application/javascript"; default: return "*/*"; } } get body() { return this.options.body; } get query() { const originalQuery = (this.originalUrl.split("?")[1] || "").split("#")[0]; const params = new URLSearchParams(originalQuery); let requestQuery = this.options.query; if (requestQuery instanceof window.FormData) { requestQuery = stringEntriesFromFormData(requestQuery); } else if (requestQuery instanceof window.URLSearchParams) { requestQuery = requestQuery.entries(); } else { requestQuery = Object.entries(requestQuery || {}); } mergeEntries(params, requestQuery); const query = params.toString(); return query.length > 0 ? `?${query}` : ""; } get url() { return this.originalUrl.split("?")[0].split("#")[0] + this.query; } get responseKind() { return this.options.responseKind || "html"; } get signal() { return this.options.signal; } get redirect() { return this.options.redirect || "follow"; } get credentials() { return this.options.credentials || "same-origin"; } get keepalive() { return this.options.keepalive || false; } get additionalHeaders() { return this.options.headers || {}; } get formattedBody() { const bodyIsAString = Object.prototype.toString.call(this.body) === "[object String]"; const contentTypeIsJson = this.headers["Content-Type"] === "application/json"; if (contentTypeIsJson && !bodyIsAString) { return JSON.stringify(this.body); } return this.body; } } async function get(url, options) { const request = new FetchRequest("get", url, options); return request.perform(); } async function post(url, options) { const request = new FetchRequest("post", url, options); return request.perform(); } async function put(url, options) { const request = new FetchRequest("put", url, options); return request.perform(); } async function patch(url, options) { const request = new FetchRequest("patch", url, options); return request.perform(); } async function destroy(url, options) { const request = new FetchRequest("delete", url, options); return request.perform(); } export { FetchRequest, FetchResponse, RequestInterceptor, destroy, get, patch, post, put }; requestjs-rails-0.0.14/app/assets/javascripts/rails-requestjs.js0000644000175000017500000000003215144147010024065 0ustar sorensoren//= require ./requestjs.jsrequestjs-rails-0.0.14/Rakefile0000644000175000017500000000032115144147010015425 0ustar sorensorenrequire "bundler/setup" require "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new(:test) do |t| t.libs << 'test' t.pattern = 'test/**/*_test.rb' t.verbose = false end task default: :testrequestjs-rails-0.0.14/README.md0000644000175000017500000000177015144147010015250 0ustar sorensoren# Request.JS for Rails [Rails Request.JS](https://github.com/rails/request.js) encapsulates the logic to send by default some headers that are required by rails applications like the `X-CSRF-Token`. ## Installation 1. Add the `requestjs-rails` gem to your Gemfile: `gem 'requestjs-rails'` 2. Run `./bin/bundle install`. 3. Run `./bin/rails requestjs:install` If using the asset pipeline to manage JavaScript, the last command will: - Append `import "@rails/request.js"` to your `app/assets/javascripts/application.js` entrypoint. Make sure you've already installed `importmap-rails` and that it's referenced before `requestjs-rails` in your Gemfile. If using Webpacker to manage JavaScript, the last command will: - Install the Request.JS NPM package. ## Usage With the installation done check the documentation in the [Rails Request.JS](https://github.com/rails/request.js#how-to-use) repository. ## License Request.JS for Rails is released under the [MIT License](https://opensource.org/licenses/MIT). requestjs-rails-0.0.14/MIT-LICENSE0000644000175000017500000000204115144147010015415 0ustar sorensorenCopyright (c) 2021 Marcelo Lauxen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.