├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json └── thumb.jpg └── src ├── App.css ├── App.js ├── App.test.js ├── index.css ├── index.js ├── json-to-simple-graphql-schema ├── index.js ├── remove-duplicate-types.js ├── stringify-schema.js └── to-schema.js ├── logo.svg └── serviceWorker.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json-to-graphql-schema-online 2 | 3 | Transforms JSON input into a GraphQL schema. 4 | 5 | URL: [https://matheusr42.github.io/json-to-graphql-schema-online/](https://matheusr42.github.io/json-to-graphql-schema-online/) 6 | 7 | ### Why would I use this? 8 | 9 | Let's say you want to use an existing REST API in a GraphQL service and expose the data that API provides. You'll need to expose that data in a GraphQL schema. Without this tool, you'll have to slog through the JSON response and manually extract and convert the relevant types to GraphQL schema types. This tool attempts to provide an automated reasonable first-pass at that effort. 10 | 11 | This app is based on [json-to-simple-graphql-schema](https://github.com/walmartlabs/json-to-simple-graphql-schema) from walmartlabs. [@maxnachlinger](https://github.com/maxnachlinger) done the real work I just implement the web interface :) 12 | 13 | 14 | When I developed this app there was only the command line interface, now [@maxnachlinger](https://github.com/maxnachlinger) created a web official version. I recommend to see his version, since it is importing the oficial lib. 15 | https://github.com/walmartlabs/json-to-simple-graphql-schema 16 | 17 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-to-graphql-schema-online", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "http://matheusr42.github.io/json-to-graphql-schema-online", 6 | "dependencies": { 7 | "lodash": "^4.17.11", 8 | "pascal-case": "^2.0.1", 9 | "react": "^16.8.6", 10 | "react-codemirror": "^1.0.0", 11 | "react-codemirror2": "^5.1.0", 12 | "react-dom": "^16.8.6", 13 | "react-scripts": "2.1.8", 14 | "react-split-pane": "^0.1.87", 15 | "sweetalert2": "^8.8.0", 16 | "util": "^0.11.1" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject", 23 | "predeploy": "npm run build", 24 | "deploy": "gh-pages -d build" 25 | }, 26 | "eslintConfig": { 27 | "extends": "react-app" 28 | }, 29 | "browserslist": [ 30 | ">0.2%", 31 | "not dead", 32 | "not ie <= 11", 33 | "not op_mini all" 34 | ], 35 | "devDependencies": { 36 | "gh-pages": "^2.0.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatheusR42/json-to-graphql-schema-online/59f34b585f466446940d509d36275bd5fdb4b6d9/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | Convert JSON to GraphQL Schema Online 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MatheusR42/json-to-graphql-schema-online/59f34b585f466446940d509d36275bd5fdb4b6d9/public/thumb.jpg -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .Resizer { 2 | background: #000; 3 | opacity: .2; 4 | z-index: 1; 5 | -moz-box-sizing: border-box; 6 | -webkit-box-sizing: border-box; 7 | box-sizing: border-box; 8 | -moz-background-clip: padding; 9 | -webkit-background-clip: padding; 10 | background-clip: padding-box; 11 | } 12 | 13 | .Resizer:hover { 14 | -webkit-transition: all 2s ease; 15 | transition: all 2s ease; 16 | } 17 | 18 | .Resizer.horizontal { 19 | height: 11px; 20 | margin: -5px 0; 21 | border-top: 5px solid rgba(255, 255, 255, 0); 22 | border-bottom: 5px solid rgba(255, 255, 255, 0); 23 | cursor: row-resize; 24 | width: 100%; 25 | } 26 | 27 | .Resizer.horizontal:hover { 28 | border-top: 5px solid rgba(0, 0, 0, 0.5); 29 | border-bottom: 5px solid rgba(0, 0, 0, 0.5); 30 | } 31 | 32 | .Resizer.vertical { 33 | width: 11px; 34 | margin: 0 -5px; 35 | border-left: 5px solid rgba(255, 255, 255, 0); 36 | border-right: 5px solid rgba(255, 255, 255, 0); 37 | cursor: col-resize; 38 | } 39 | 40 | .Resizer.vertical:hover { 41 | border-left: 5px solid rgba(0, 0, 0, 0.5); 42 | border-right: 5px solid rgba(0, 0, 0, 0.5); 43 | } 44 | .Resizer.disabled { 45 | cursor: not-allowed; 46 | } 47 | .Resizer.disabled:hover { 48 | border-color: transparent; 49 | } 50 | 51 | .CodeMirror { 52 | /* Set height, width, borders, and global font properties here */ 53 | font-family: monospace; 54 | height: calc(100vh - 10px); 55 | color: black; 56 | } 57 | 58 | h1 { 59 | padding: 0; 60 | margin: 0; 61 | } 62 | 63 | h2 { 64 | border-bottom: 1px solid #000; 65 | padding: 10px; 66 | margin: 0; 67 | } 68 | 69 | header { 70 | text-align: center; 71 | border-bottom: 1px solid #000; 72 | padding: 20px; 73 | } 74 | 75 | 76 | button { 77 | font-size: 24px; 78 | text-transform: uppercase; 79 | margin-top: 20px; 80 | } 81 | 82 | .source-code { 83 | position: fixed; 84 | bottom: 10px; 85 | right: 10px; 86 | z-index: 99; 87 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Swal from 'sweetalert2' 3 | import SplitPane from 'react-split-pane'; 4 | import {Controlled as CodeMirror} from 'react-codemirror2'; 5 | 6 | import { 7 | jsonToSchema 8 | } from './json-to-simple-graphql-schema'; 9 | 10 | import 'codemirror/lib/codemirror.css'; 11 | import './App.css'; 12 | 13 | class App extends Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | this.state = { 18 | jsonInput: 19 | `{ 20 | "userId": 1, 21 | "id": 1, 22 | "addresses": [ 23 | { 24 | "id": 2, 25 | "name": "two" 26 | }, 27 | { 28 | "id": 1, 29 | "name": "one" 30 | } 31 | ] 32 | }` 33 | , 34 | schema: 35 | `type Addresses { 36 | id: Int 37 | name: String 38 | } 39 | 40 | type AutogeneratedMainType { 41 | userId: Int 42 | id: Int 43 | addresses: [Addresses] 44 | }` 45 | } 46 | } 47 | 48 | convert() { 49 | const { jsonInput } = this.state; 50 | 51 | const { error, value } = jsonToSchema({ jsonInput }); 52 | if (error) { 53 | const { title, message } = error; 54 | 55 | Swal.fire({ 56 | title, 57 | text: message, 58 | type: 'error' 59 | }) 60 | 61 | return; 62 | } 63 | 64 | this.setState({ schema: value }); 65 | } 66 | 67 | render() { 68 | const width = window.innerWidth / 2; 69 | const { jsonInput, schema } = this.state; 70 | 71 | return ( 72 |
73 |
74 |

Convert JSON to GraphQL Schema Online

75 | 76 |
77 | 78 | 79 | 80 |
81 |

JSON Input

82 | 83 | { 85 | this.setState({ jsonInput: value}); 86 | }} 87 | onChange={(editor, data, value) => { 88 | }} 89 | value={jsonInput} 90 | options={{ mode: 'json', lineNumbers: true, tabSize: 2 }} 91 | /> 92 |
93 |
94 |

GraphQL schema

95 | { 97 | this.setState({ schema: value}); 98 | }} 99 | onChange={(editor, data, value) => { 100 | }} 101 | value={schema} 102 | options={{ lineNumbers: true, tabSize: 2 }} 103 | /> 104 |
105 |
106 | 107 | 112 | Source code 113 | 114 |
115 | ); 116 | } 117 | } 118 | 119 | export default App; 120 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /src/json-to-simple-graphql-schema/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) [2018]-present, Walmart Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License." 15 | */ 16 | 17 | "use strict"; 18 | 19 | import toSchema from "./to-schema"; 20 | import stringifySchema from "./stringify-schema"; 21 | 22 | const safeFnExecute = fn => { 23 | try { 24 | return { value: fn() }; 25 | } catch (error) { 26 | return { error }; 27 | } 28 | }; 29 | 30 | const validateJson = jsonInput => { 31 | const { error: jsonError, value } = safeFnExecute(() => JSON.parse(jsonInput)); 32 | 33 | if (jsonError) { 34 | return { 35 | error: { 36 | title: 'Invalid JSON received', 37 | message: jsonError.message 38 | } 39 | }; 40 | } 41 | 42 | if (!value) { 43 | return { 44 | error: { 45 | title: 'Invalid JSON received', 46 | message: null 47 | } 48 | }; 49 | } 50 | 51 | return { value }; 52 | }; 53 | 54 | const jsonToSchema = ({ baseType = "AutogeneratedMainType", jsonInput }) => { 55 | const { error, value } = validateJson(jsonInput); 56 | if (error) { 57 | return { error }; 58 | } 59 | 60 | return safeFnExecute(() => stringifySchema(baseType, toSchema(value))); 61 | }; 62 | 63 | export { 64 | jsonToSchema, 65 | validateJson 66 | }; 67 | -------------------------------------------------------------------------------- /src/json-to-simple-graphql-schema/remove-duplicate-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) [2018]-present, Walmart Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License." 15 | */ 16 | 17 | "use strict"; 18 | 19 | const combineTypes = str => 20 | str 21 | .split("type ") 22 | // clear empty lines 23 | .filter(i => i) 24 | .reduce((accum, fragment, idx) => { 25 | const full = `type ${fragment}`.trim(); 26 | 27 | const fragmentArr = fragment 28 | .trim() 29 | .split(" ") 30 | .map(s => s.trim()); 31 | const name = fragmentArr.shift(); 32 | const amtFields = fragmentArr.filter(s => s.indexOf(":") > -1).length; 33 | 34 | // keep the type with the most fields 35 | if (!accum[name] || accum[name].amtFields < amtFields) { 36 | accum[name] = { full, name, amtFields, idx }; 37 | } 38 | 39 | return accum; 40 | }, {}); 41 | 42 | const extractPossibleIdenticalTypes = str => 43 | str 44 | .split("type ") 45 | // clear empty lines 46 | .filter(i => i) 47 | .reduce((accum, fragment) => { 48 | const fragmentArr = fragment 49 | .trim() 50 | .split(" ") 51 | .map(s => s.trim()); 52 | const name = fragmentArr.shift(); 53 | const fieldsSignature = fragmentArr.join(" "); 54 | 55 | if (!accum[fieldsSignature]) { 56 | accum[fieldsSignature] = [name]; 57 | return accum; 58 | } 59 | 60 | if (accum[fieldsSignature].indexOf(name) === -1) { 61 | accum[fieldsSignature].push(name); 62 | } 63 | return accum; 64 | }, {}); 65 | 66 | const removeDuplicateTypes = str => { 67 | const schemaStr = Object.values(combineTypes(str)) 68 | .sort((a0, a1) => a0.idx - a1.idx) 69 | .map(({ full }) => full) 70 | .join("\n\n"); 71 | 72 | const probablyDuplicates = Object.values(extractPossibleIdenticalTypes(schemaStr)) 73 | .filter(arr => arr.length > 1) 74 | .map(arr => `# ${arr.join(" ")}`) 75 | .join("\n"); 76 | 77 | return `${schemaStr}${ 78 | probablyDuplicates ? `\n\n# Types with identical fields:\n${probablyDuplicates}` : "" 79 | }`; 80 | }; 81 | 82 | export default removeDuplicateTypes; 83 | -------------------------------------------------------------------------------- /src/json-to-simple-graphql-schema/stringify-schema.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) [2018]-present, Walmart Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License." 15 | */ 16 | 17 | "use strict"; 18 | 19 | // import util from "util"; 20 | import _ from "lodash"; 21 | import pascalCase from "pascal-case"; 22 | import removeDuplicateTypes from "./remove-duplicate-types"; 23 | 24 | const schemaToString = (typeName, obj) => { 25 | let str = ""; 26 | 27 | Object.keys(obj).forEach(key => { 28 | if (_.isArray(obj[key])) { 29 | const firstElement = obj[key][0]; 30 | 31 | if (_.isArray(firstElement) || _.isObject(firstElement)) { 32 | const newTypeName = pascalCase(key); 33 | str += schemaToString(newTypeName, firstElement); 34 | obj[key][0] = newTypeName; 35 | } 36 | return; 37 | } 38 | 39 | if (_.isObject(obj[key])) { 40 | const newTypeName = pascalCase(key); 41 | str += schemaToString(newTypeName, obj[key]); 42 | obj[key] = newTypeName; 43 | } 44 | }); 45 | 46 | //original implementation to node 47 | // const newObjString = util.inspect(obj, { depth: null, compact: false }); 48 | 49 | // return `${str}type ${typeName} ${newObjString.replace(/'/g, "")} ` 50 | // .replace(/\[\n/g, "[") 51 | // .replace(/\[\s+/g, "[") 52 | // .replace(/\n\s+\]/g, "]") 53 | // .replace(/,/g, "") 54 | // .replace(/ {3,}/g, " "); 55 | 56 | //adaptation to web 57 | const newObjString = JSON.stringify(obj, null, 4); 58 | 59 | return `${str}type ${typeName} ${newObjString.replace(/"/g, "")} ` 60 | .replace(/\[\n/g, "[") 61 | .replace(/\[\s+/g, "[") 62 | .replace(/\n\s+\]/g, "]") 63 | .replace(/,/g, "") 64 | .replace(/ {3,}/g, " "); 65 | }; 66 | 67 | const stringifySchema = _.flow([schemaToString, removeDuplicateTypes]); 68 | 69 | export default stringifySchema; 70 | 71 | -------------------------------------------------------------------------------- /src/json-to-simple-graphql-schema/to-schema.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) [2018]-present, Walmart Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License." 15 | */ 16 | 17 | "use strict"; 18 | 19 | import _ from "lodash"; 20 | 21 | const transformPrimitive = (key, value) => { 22 | if (_.isInteger(value)) { 23 | return "Int"; 24 | } 25 | if (_.isBoolean(value)) { 26 | return "Boolean"; 27 | } 28 | if (_.isNumber(value)) { 29 | return "Float"; 30 | } 31 | return "String"; 32 | }; 33 | 34 | const toSchema = input => { 35 | const result = {}; 36 | const processedItemsCache = []; 37 | const stack = [{ obj: input, path: "" }]; 38 | 39 | while (stack.length > 0) { 40 | const { obj, path } = stack.pop(); 41 | 42 | Object.keys(obj).forEach(key => { 43 | let currentValue = obj[key]; 44 | 45 | if (!_.isArray(currentValue) && !_.isObject(currentValue)) { 46 | const newObjValue = transformPrimitive(key, currentValue); 47 | const newObjValuePath = path ? `${path}.` : ""; 48 | _.set(result, `${newObjValuePath}${key}`, newObjValue); 49 | return; 50 | } 51 | 52 | // all this to guard against circular refs 53 | if (_.some(processedItemsCache, o => o === currentValue)) { 54 | return; 55 | } 56 | 57 | processedItemsCache.push(currentValue); 58 | 59 | const pathPrefix = path ? `${path}.` : ""; 60 | const newPath = `${pathPrefix}${key}`; 61 | 62 | // only use the first element in the array since we're assuming the same type for all 63 | // array elements 64 | if (_.isArray(currentValue)) { 65 | currentValue = [currentValue[0]]; 66 | } 67 | 68 | stack.push({ 69 | obj: currentValue, 70 | path: newPath 71 | }); 72 | }); 73 | } 74 | 75 | processedItemsCache.splice(0, processedItemsCache.length); 76 | return result; 77 | }; 78 | 79 | export default toSchema; 80 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | --------------------------------------------------------------------------------