├── .gitignore ├── README.md ├── index.ts ├── package-lock.json ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > `fetch` has a opaque mode by passing `{ mode: 'no-cors' }` option. In that mode, you could achieve the same results (send a cross-domain request without access to a response) as with that library. 2 | 3 | ### Cross-domain requests without CORS-support 🏓 4 | Do you need to make a request (POST or GET) from browser to external resource **without CORS**? 5 | 6 | If you don't have access for the requested resource and not able to setup the correct `CORS policy`, use `jsonp` or make [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) transport – that simple, lightweight (2kb) and zero-dependency library could help you. 7 | 8 | You could make an ajax-like cross-domain request, but without **receiving the response**, because of browser's XSS-protection. 9 | 10 | Example ([sandbox.io](https://codesandbox.io/s/stoic-pare-2di75?file=/src/index.js)): 11 | ```js 12 | import CrossDomainRequest from 'cross-domain-request' 13 | 14 | const data = { 15 | foo: 'bar' 16 | } 17 | CrossDomainRequest('https://reqbin.com/echo/post/form', data) 18 | ``` 19 | 20 | Parameters: 21 | ```js 22 | CrossDomainRequest(url, data, options) 23 | ``` 24 | - `url` (string) - requested url for submit data; 25 | - `data` (Object); 26 | - `options` ([Object]) - options with method, eg GET (`{ method: 'POST' }` by default); 27 | 28 | How it works and what's happened under the hood: 29 | - Make a hidden form; 30 | - Make an iframe and connect with the form by [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-target) attribute for prevent reload/open new browser tab; 31 | - Fill the form data and submit to passed url, but with `target` page won't be reloaded and result will be in the iframe; 32 | - Clean all artifacts. 33 | 34 | #### TODO: 35 | - [ ] custom function for serialize nested data 36 | - [ ] tests 37 | - [ ] ability to pass FormData 38 | 39 | #### References 40 | - [post-robot](https://github.com/krakenjs/post-robot) - Cross-domain post-messaging library 41 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | enum Method { 2 | GET = 'GET', 3 | POST = 'POST', 4 | } 5 | 6 | type Options = { 7 | method?: Method 8 | } 9 | 10 | const DEFAULT_OPTIONS: Options = { 11 | method: Method.POST, 12 | } 13 | 14 | const HIDDEN_STYLES = { 15 | display: 'none', 16 | position: 'absolute', 17 | left: '-900px', 18 | top: '-900px', 19 | } 20 | 21 | const HIDDEN_STYLES_STRING = Object.entries(HIDDEN_STYLES).map(([prop, val]) => `${prop}: ${val}`).join(';') 22 | 23 | /** 24 | * Cross-domain request without CORS 25 | * @param {string} url URL for submission 26 | * @param {Object} data Submission data 27 | * @param {Object} [options={}] Form options 28 | */ 29 | export default (url: string, data: {}, options: Options = {}): void => { 30 | if (!url) throw new Error('Missing url param') 31 | options = { ...DEFAULT_OPTIONS, ...options } 32 | 33 | let isFormSubmited = false 34 | const id = String(Math.random()).replace('.', '') 35 | const frameId = `frame${id}` 36 | const formId = `form${id}` 37 | const container = document.createRange().createContextualFragment(` 38 |
39 | 40 |
41 | ${data ? Object.entries(data).map(([fieldName, value]) => 42 | `` 43 | ) : ''} 44 |
45 |
46 | `) 47 | document.body.appendChild(container) 48 | 49 | const containerEl = document.getElementById(id) 50 | const form = document.getElementById(formId) as HTMLFormElement 51 | const frame = document.getElementById(frameId) as HTMLIFrameElement 52 | 53 | if (!containerEl || !form || !frame) { 54 | throw new Error("Unable to create elements for transport"); 55 | } 56 | 57 | // FIXME: rewrite to https://developer.mozilla.org/en-US/docs/Web/API/Document/readystatechange_event 58 | frame.addEventListener('load', () => { 59 | // Clean up 60 | if (isFormSubmited) document.body.removeChild(containerEl) 61 | }); 62 | form.submit() 63 | isFormSubmited = true 64 | } 65 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "post-transport", 3 | "version": "1.0.2-0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "typescript": { 8 | "version": "3.9.7", 9 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", 10 | "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cross-domain-request", 3 | "version": "1.0.2-0", 4 | "description": "Make POST/GET cross-domain requests without CORS", 5 | "keywords": [ 6 | "CORS", 7 | "cross domain request", 8 | "post request" 9 | ], 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/fliptheweb/cross-domain-request.git" 15 | }, 16 | "scripts": { 17 | "build": "tsc", 18 | "test": "echo \"Error: no test specified\" && exit 1", 19 | "prepare": "npm run build", 20 | "version": "git add -A .", 21 | "postversion": "git push && git push --tags" 22 | }, 23 | "author": "Artur Kornakov", 24 | "license": "MIT", 25 | "devDependencies": { 26 | "typescript": "^3.9.7" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | "lib": ["es2017", "dom"], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 68 | } 69 | } 70 | --------------------------------------------------------------------------------