├── .all-contributorsrc ├── .circleci └── config.yml ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── stale.yml ├── .gitignore ├── .prettierrc.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── __mocks__ └── @react-native-community │ └── netinfo.ts ├── assets └── cover_rn_offline.png ├── babel.config.js ├── example ├── .gitignore ├── .watchmanconfig ├── App.tsx ├── DummyNetworkContext.tsx ├── README.md ├── __tests__ │ └── App-test.tsx ├── app.json ├── assets │ ├── fonts │ │ └── SpaceMono-Regular.ttf │ └── images │ │ ├── icon.png │ │ ├── redux-saga.png │ │ ├── redux.png │ │ ├── robot-dev.png │ │ ├── robot-prod.png │ │ └── splash.png ├── babel.config.js ├── components │ ├── ActionButtons.tsx │ ├── ConnectionToggler.tsx │ ├── Counter.tsx │ ├── OfflineQueue.tsx │ ├── ReduxNetworkReader.tsx │ ├── StyledText.tsx │ ├── TabBarIcon.tsx │ └── __tests__ │ │ └── StyledText-test.tsx ├── constants │ ├── Colors.ts │ └── Layout.ts ├── navigation │ ├── AppNavigator.tsx │ └── MainTabNavigator.tsx ├── package-lock.json ├── package.json ├── redux │ ├── actions.ts │ ├── createStore.ts │ ├── reducer.ts │ └── sagas.ts └── screens │ ├── ComponentsScreen.tsx │ ├── ReduxScreen.tsx │ └── SagasScreen.tsx ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── components │ ├── NetworkConnectivity.tsx │ ├── NetworkConsumer.tsx │ ├── NetworkContext.tsx │ ├── NetworkProvider.tsx │ └── ReduxNetworkProvider.tsx ├── hooks │ └── useIsConnected.ts ├── index.ts ├── redux │ ├── actionCreators.ts │ ├── actionTypes.ts │ ├── createNetworkMiddleware.ts │ ├── createReducer.ts │ └── sagas.ts ├── types.ts └── utils │ ├── checkConnectivityInterval.ts │ ├── checkInternetAccess.ts │ ├── checkInternetConnection.ts │ ├── constants.ts │ ├── getSimilarActionInQueue.ts │ ├── makeHttpRequest.ts │ ├── nonNullable.ts │ ├── objectEntries.ts │ └── wait.ts ├── test ├── NetworkConnectivity.test.tsx ├── NetworkConsumer.test.tsx ├── NetworkProvider.test.tsx ├── ReduxNetworkProvider.test.tsx ├── __snapshots__ │ ├── NetworkConnectivity.test.tsx.snap │ ├── NetworkProvider.test.tsx.snap │ └── ReduxNetworkProvider.test.tsx.snap ├── checkConnectivityInterval.test.ts ├── checkInternetAccess.test.ts ├── checkInternetConnection.test.ts ├── createNetworkMiddleware.test.ts ├── getSimilarActionInQueue.test.ts ├── makeHttpRequest.test.ts ├── reducer.test.ts ├── sagaChannels.test.ts ├── sagas.test.ts ├── setupMocks.ts └── setupTestEnv.ts ├── tsconfig.json └── typings ├── global.d.ts └── react-navigation.d.ts /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "react-native-offline", 3 | "projectOwner": "rgommezz", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "contributors": [ 12 | { 13 | "login": "rgommezz", 14 | "name": "Raúl Gómez Acuña", 15 | "avatar_url": "https://avatars0.githubusercontent.com/u/4982414?v=4", 16 | "profile": "https://twitter.com/rgommezz", 17 | "contributions": [ 18 | "code", 19 | "doc", 20 | "example", 21 | "ideas", 22 | "review", 23 | "question" 24 | ] 25 | }, 26 | { 27 | "login": "piotrwitek", 28 | "name": "Piotrek Witek", 29 | "avatar_url": "https://avatars0.githubusercontent.com/u/739075?v=4", 30 | "profile": "https://github.com/piotrwitek", 31 | "contributions": [ 32 | "doc" 33 | ] 34 | }, 35 | { 36 | "login": "adrienthiery", 37 | "name": "Adrien Thiery", 38 | "avatar_url": "https://avatars3.githubusercontent.com/u/2392118?v=4", 39 | "profile": "http://www.thiery.io", 40 | "contributions": [ 41 | "code", 42 | "doc" 43 | ] 44 | }, 45 | { 46 | "login": "hasibsahibzada", 47 | "name": "Hasibullah Sahibzada", 48 | "avatar_url": "https://avatars0.githubusercontent.com/u/6321988?v=4", 49 | "profile": "https://github.com/hasibsahibzada", 50 | "contributions": [ 51 | "code" 52 | ] 53 | }, 54 | { 55 | "login": "macrozone", 56 | "name": "Marco Wettstein", 57 | "avatar_url": "https://avatars3.githubusercontent.com/u/1972353?v=4", 58 | "profile": "https://www.linkedin.com/in/marco-wettstein-b1b8938b?trk=hp-identity-name", 59 | "contributions": [ 60 | "code" 61 | ] 62 | }, 63 | { 64 | "login": "kentos", 65 | "name": "Kent Cederström", 66 | "avatar_url": "https://avatars1.githubusercontent.com/u/1098636?v=4", 67 | "profile": "http://kentcederstrom.se", 68 | "contributions": [ 69 | "code" 70 | ] 71 | }, 72 | { 73 | "login": "richardvclam", 74 | "name": "Richard V. Lam", 75 | "avatar_url": "https://avatars0.githubusercontent.com/u/16093452?v=4", 76 | "profile": "https://richardvclam.github.io", 77 | "contributions": [ 78 | "doc" 79 | ] 80 | }, 81 | { 82 | "login": "dickerpulli", 83 | "name": "Thomas Bosch", 84 | "avatar_url": "https://avatars1.githubusercontent.com/u/1300393?v=4", 85 | "profile": "http://www.codecentric.de", 86 | "contributions": [ 87 | "code", 88 | "doc" 89 | ] 90 | }, 91 | { 92 | "login": "cinan", 93 | "name": "cinan", 94 | "avatar_url": "https://avatars2.githubusercontent.com/u/1828134?v=4", 95 | "profile": "http://blog.cinan.sk", 96 | "contributions": [ 97 | "code" 98 | ] 99 | }, 100 | { 101 | "login": "YKSing", 102 | "name": "Colon D", 103 | "avatar_url": "https://avatars2.githubusercontent.com/u/3830084?v=4", 104 | "profile": "https://github.com/YKSing", 105 | "contributions": [ 106 | "code" 107 | ] 108 | }, 109 | { 110 | "login": "SKempin", 111 | "name": "Stephen Kempin", 112 | "avatar_url": "https://avatars1.githubusercontent.com/u/10877466?v=4", 113 | "profile": "http://www.stephenkempin.co.uk", 114 | "contributions": [ 115 | "doc" 116 | ] 117 | }, 118 | { 119 | "login": "tscharke", 120 | "name": "Thomas Scharke", 121 | "avatar_url": "https://avatars0.githubusercontent.com/u/5890860?v=4", 122 | "profile": "https://www.linkedin.com/in/tscharke/", 123 | "contributions": [ 124 | "code", 125 | "doc" 126 | ] 127 | }, 128 | { 129 | "login": "felipemartim", 130 | "name": "felipemartim", 131 | "avatar_url": "https://avatars3.githubusercontent.com/u/570829?v=4", 132 | "profile": "https://github.com/felipemartim", 133 | "contributions": [ 134 | "code", 135 | "doc" 136 | ] 137 | }, 138 | { 139 | "login": "maieonbrix", 140 | "name": "Mehdi A.", 141 | "avatar_url": "https://avatars1.githubusercontent.com/u/16226376?v=4", 142 | "profile": "http://WIP", 143 | "contributions": [ 144 | "code" 145 | ] 146 | }, 147 | { 148 | "login": "comur", 149 | "name": "Can OMUR", 150 | "avatar_url": "https://avatars0.githubusercontent.com/u/1212381?v=4", 151 | "profile": "https://github.com/comur", 152 | "contributions": [ 153 | "code" 154 | ] 155 | }, 156 | { 157 | "login": "markvl91", 158 | "name": "Mark van Lagen", 159 | "avatar_url": "https://avatars3.githubusercontent.com/u/5470392?v=4", 160 | "profile": "http://nijhuisenvanlagen.nl", 161 | "contributions": [ 162 | "code" 163 | ] 164 | }, 165 | { 166 | "login": "gtfargo", 167 | "name": "George Farro", 168 | "avatar_url": "https://avatars2.githubusercontent.com/u/2320535?v=4", 169 | "profile": "https://github.com/gtfargo", 170 | "contributions": [ 171 | "code" 172 | ] 173 | }, 174 | { 175 | "login": "mleduque", 176 | "name": "Mickaël Leduque", 177 | "avatar_url": "https://avatars2.githubusercontent.com/u/444063?v=4", 178 | "profile": "https://github.com/mleduque", 179 | "contributions": [ 180 | "code" 181 | ] 182 | }, 183 | { 184 | "login": "florentroques", 185 | "name": "Florent Roques", 186 | "avatar_url": "https://avatars3.githubusercontent.com/u/1901827?v=4", 187 | "profile": "https://stackoverflow.com/users/1152843/florent-roques?tab=profile", 188 | "contributions": [ 189 | "doc" 190 | ] 191 | }, 192 | { 193 | "login": "Krizzu", 194 | "name": "Krzysztof Borowy", 195 | "avatar_url": "https://avatars2.githubusercontent.com/u/6444719?v=4", 196 | "profile": "https://github.com/Krizzu", 197 | "contributions": [ 198 | "code", 199 | "doc" 200 | ] 201 | }, 202 | { 203 | "login": "DCKT", 204 | "name": "Thomas Deconinck", 205 | "avatar_url": "https://avatars2.githubusercontent.com/u/1548421?v=4", 206 | "profile": "http://www.thomasdeconinck.fr", 207 | "contributions": [ 208 | "code", 209 | "doc" 210 | ] 211 | }, 212 | { 213 | "login": "thymikee", 214 | "name": "Michał Pierzchała", 215 | "avatar_url": "https://avatars2.githubusercontent.com/u/5106466?v=4", 216 | "profile": "https://buymeacoff.ee/thymikee", 217 | "contributions": [ 218 | "code" 219 | ] 220 | }, 221 | { 222 | "login": "imartingraham", 223 | "name": "Ian Graham", 224 | "avatar_url": "https://avatars3.githubusercontent.com/u/119142?v=4", 225 | "profile": "https://github.com/imartingraham", 226 | "contributions": [ 227 | "code" 228 | ] 229 | }, 230 | { 231 | "login": "pettersamuelsen", 232 | "name": "Petter Samuelsen", 233 | "avatar_url": "https://avatars2.githubusercontent.com/u/1244867?v=4", 234 | "profile": "http://www.pettersamuelsen.com", 235 | "contributions": [ 236 | "code" 237 | ] 238 | }, 239 | { 240 | "login": "usrbowe", 241 | "name": "Lukas Kurucz", 242 | "avatar_url": "https://avatars1.githubusercontent.com/u/5339061?v=4", 243 | "profile": "https://github.com/usrbowe", 244 | "contributions": [ 245 | "code" 246 | ] 247 | }, 248 | { 249 | "login": "Norris1z", 250 | "name": "Norris Oduro", 251 | "avatar_url": "https://avatars1.githubusercontent.com/u/18237132?v=4", 252 | "profile": "https://twitter.com/norris1z", 253 | "contributions": [ 254 | "code" 255 | ] 256 | }, 257 | { 258 | "login": "richardtks", 259 | "name": "Richard Tan", 260 | "avatar_url": "https://avatars3.githubusercontent.com/u/43637878?v=4", 261 | "profile": "https://github.com/richardtks", 262 | "contributions": [ 263 | "ideas" 264 | ] 265 | }, 266 | { 267 | "login": "Jimbomaniak", 268 | "name": "Oleg Kupriianov", 269 | "avatar_url": "https://avatars3.githubusercontent.com/u/17765105?v=4", 270 | "profile": "https://twitter.com/tysh_pysh", 271 | "contributions": [ 272 | "code" 273 | ] 274 | }, 275 | { 276 | "login": "reilem", 277 | "name": "reilem", 278 | "avatar_url": "https://avatars1.githubusercontent.com/u/11155505?v=4", 279 | "profile": "https://github.com/reilem", 280 | "contributions": [ 281 | "ideas" 282 | ] 283 | }, 284 | { 285 | "login": "jozr", 286 | "name": "Josephine Wright", 287 | "avatar_url": "https://avatars1.githubusercontent.com/u/8154741?v=4", 288 | "profile": "https://github.com/jozr", 289 | "contributions": [ 290 | "doc" 291 | ] 292 | }, 293 | { 294 | "login": "umbrella-kirill-karpov", 295 | "name": "Kirill Karpov", 296 | "avatar_url": "https://avatars0.githubusercontent.com/u/16078455?v=4", 297 | "profile": "http://umbrellait.com", 298 | "contributions": [ 299 | "code" 300 | ] 301 | }, 302 | { 303 | "login": "LiquidSean", 304 | "name": "Sean Luthjohn", 305 | "avatar_url": "https://avatars3.githubusercontent.com/u/1811319?v=4", 306 | "profile": "https://github.com/LiquidSean", 307 | "contributions": [ 308 | "code", 309 | "doc" 310 | ] 311 | }, 312 | { 313 | "login": "cuttlas", 314 | "name": "cuttlas", 315 | "avatar_url": "https://avatars2.githubusercontent.com/u/1228574?v=4", 316 | "profile": "https://github.com/cuttlas", 317 | "contributions": [ 318 | "doc" 319 | ] 320 | }, 321 | { 322 | "login": "emanueleDiVizio", 323 | "name": "Emanuele", 324 | "avatar_url": "https://avatars0.githubusercontent.com/u/9089203?v=4", 325 | "profile": "https://github.com/emanueleDiVizio", 326 | "contributions": [ 327 | "code", 328 | "doc" 329 | ] 330 | }, 331 | { 332 | "login": "helderburato", 333 | "name": "Helder Burato Berto", 334 | "avatar_url": "https://avatars3.githubusercontent.com/u/862575?v=4", 335 | "profile": "https://helder.dev", 336 | "contributions": [ 337 | "doc" 338 | ] 339 | }, 340 | { 341 | "login": "ankeetmaini", 342 | "name": "Ankeet Maini", 343 | "avatar_url": "https://avatars1.githubusercontent.com/u/6652823?v=4", 344 | "profile": "http://ankeetmaini.github.io/", 345 | "contributions": [ 346 | "code" 347 | ] 348 | }, 349 | { 350 | "login": "nipuna777", 351 | "name": "Nipuna Gunathilake", 352 | "avatar_url": "https://avatars0.githubusercontent.com/u/5859290?v=4", 353 | "profile": "http://www.nipuna777.com", 354 | "contributions": [ 355 | "doc" 356 | ] 357 | }, 358 | { 359 | "login": "1ike", 360 | "name": "1ike", 361 | "avatar_url": "https://avatars1.githubusercontent.com/u/10694949?v=4", 362 | "profile": "https://github.com/1ike", 363 | "contributions": [ 364 | "code" 365 | ] 366 | }, 367 | { 368 | "login": "JH108", 369 | "name": "Jesse Hill", 370 | "avatar_url": "https://avatars1.githubusercontent.com/u/14010157?v=4", 371 | "profile": "https://github.com/JH108", 372 | "contributions": [ 373 | "doc" 374 | ] 375 | }, 376 | { 377 | "login": "ugurakkurt", 378 | "name": "ugur akkurt", 379 | "avatar_url": "https://avatars0.githubusercontent.com/u/12188837?v=4", 380 | "profile": "https://github.com/ugurakkurt", 381 | "contributions": [ 382 | "code", 383 | "doc" 384 | ] 385 | }, 386 | { 387 | "login": "caiangums", 388 | "name": "Ilê Caian", 389 | "avatar_url": "https://avatars2.githubusercontent.com/u/7551787?v=4", 390 | "profile": "https://github.com/caiangums", 391 | "contributions": [ 392 | "doc" 393 | ] 394 | }, 395 | { 396 | "login": "sbelzile-nexapp", 397 | "name": "Sébastien Belzile", 398 | "avatar_url": "https://avatars.githubusercontent.com/u/67745993?v=4", 399 | "profile": "https://github.com/sbelzile-nexapp", 400 | "contributions": [ 401 | "doc", 402 | "ideas" 403 | ] 404 | } 405 | ], 406 | "contributorsPerLine": 7, 407 | "skipCi": true 408 | } 409 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/rgommezz/react-native-offline 5 | parallelism: 1 6 | shell: /bin/bash --login 7 | environment: 8 | CIRCLE_ARTIFACTS: /tmp/circleci-artifacts 9 | CIRCLE_TEST_REPORTS: /tmp/circleci-test-results 10 | docker: 11 | - image: circleci/build-image:ubuntu-14.04-XXL-upstart-1189-5614f37 12 | steps: 13 | - checkout 14 | - run: mkdir -p $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS 15 | - run: 16 | working_directory: ~/rgommezz/react-native-offline 17 | command: nvm install 9.10.0 && nvm alias default 9.10.0 18 | - run: npm i 19 | # Restore the dependency cache 20 | - restore_cache: 21 | keys: 22 | # This branch if available 23 | - v3-dep-{{ .Branch }}- 24 | # Default branch if not 25 | - v3-dep-master- 26 | # Any branch if there are none on the default branch - this should be unnecessary if you have your default branch configured correctly 27 | - v3-dep- 28 | # Save dependency cache 29 | - save_cache: 30 | key: v3-dep-{{ .Branch }}-{{ epoch }} 31 | paths: 32 | # This is a broad list of cache paths to include many possible development environments 33 | # You can probably delete some of these entries 34 | - vendor/bundle 35 | - ~/virtualenvs 36 | - ~/.m2 37 | - ~/.ivy2 38 | - ~/.bundle 39 | - ~/.go_workspace 40 | - ~/.gradle 41 | - ~/.cache/bower 42 | - ./node_modules 43 | # Test 44 | - run: npm run lint 45 | - run: npm run ts 46 | - run: npm run test:coverage 47 | # Teardown 48 | # Save test results 49 | - store_test_results: 50 | path: /tmp/circleci-test-results 51 | # Save artifacts 52 | - store_artifacts: 53 | path: /tmp/circleci-artifacts 54 | - store_artifacts: 55 | path: /tmp/circleci-test-results 56 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | flow-typed/ 4 | test/__snapshots__/ 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb", 4 | "plugin:@typescript-eslint/recommended", 5 | "plugin:react/recommended", 6 | "prettier", 7 | "prettier/react", 8 | "prettier/@typescript-eslint" 9 | ], 10 | "settings": { 11 | "import/resolver": { 12 | "node": { 13 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 14 | } 15 | } 16 | }, 17 | "parser": "@typescript-eslint/parser", 18 | "plugins": ["react", "@typescript-eslint", "prettier"], 19 | "parserOptions": { 20 | "ecmaVersion": 6, 21 | "sourceType": "module", 22 | "ecmaFeatures": { 23 | "jsx": true 24 | } 25 | }, 26 | "env": { 27 | "es6": true, 28 | "node": true, 29 | "jest": true, 30 | "browser": true // This is necessary for React Native XMLHttpRequest, see https://github.com/eslint/eslint/issues/4015 31 | }, 32 | "rules": { 33 | "prettier/prettier": [ 34 | "error", 35 | { 36 | "trailingComma": "all", 37 | "singleQuote": true 38 | } 39 | ], 40 | "import/extensions": 0, 41 | "no-use-before-define": 0, 42 | "import/no-extraneous-dependencies": 0, 43 | "import/prefer-default-export": 0, 44 | "global-require": 0, 45 | "prefer-promise-reject-errors": 0, 46 | "react/prop-types": 0, 47 | "react/jsx-filename-extension": 0, 48 | "react/require-default-props": 0, 49 | "react/display-name": 0, 50 | "react/sort-comp": 0, 51 | "react/destructuring-assignment": 0, 52 | "@typescript-eslint/no-use-before-define": 0, 53 | "no-return-assign": 0, 54 | "no-undef": 0, 55 | "@typescript-eslint/ban-ts-ignore": 1 56 | }, 57 | "globals": { 58 | "jest": true 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Current Behavior 2 | 3 | - What code are you running and what is happening? 4 | - Include a screenshot if it makes sense. 5 | 6 | ### Expected Behavior 7 | 8 | - What do you expect should be happening? 9 | - Include a screenshot if it makes sense. 10 | 11 | ### How to reproduce 12 | 13 | - You must provide a way to reproduce the problem. 14 | - Either re-create the bug on [Snack](https://snack.expo.io) or link to a GitHub repository with code that reproduces the bug. 15 | - Explain how to run the example app and any steps that we need to take to reproduce the issue from the example app. 16 | 17 | ### Your Environment 18 | 19 | | software | version 20 | | ---------------- | ------- 21 | | react-native-offline | 22 | | react-native | 23 | | node | 24 | | npm or yarn | 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please provide enough information so that others can review your pull request: 2 | 3 | ## Motivation 4 | 5 | Explain the **motivation** for making this change. What existing problem does the pull request solve? 6 | 7 | ## Test plan 8 | 9 | Demonstrate the code is solid and make sure you add tests to cover the new functionality. 10 | 11 | The code must pass tests. 12 | 13 | ## Code formatting 14 | 15 | Look around. Match the style of the rest of the codebase. Run `yarn format` before committing. 16 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: > 18 | Closing this issue after a prolonged period of inactivity. If this is still present in the latest release, please feel free to create a new issue with up-to-date information. 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node.js 2 | node_modules/ 3 | npm-debug.log 4 | 5 | #jest 6 | coverage/ 7 | .jest/ 8 | dist/ 9 | 10 | # OSX 11 | # 12 | .DS_Store 13 | 14 | #IntelliJ 15 | .idea 16 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 1. Make sure you have read and understand the **Motivation** section to be aligned with project history and goals. 3 | 2. Before submitting a PR please comment in the relevant issue (or create a new one if it doesn't exist yet) to discuss all the requirements (this will prevent rejecting the PR and wasting your work). 4 | 3. All workflow scripts (prettier, linter, tests) must pass successfully. 5 | 4. Provide relevant code coverage by means of unit tests. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Raúl Gómez Acuña 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__mocks__/@react-native-community/netinfo.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | addEventListener: jest.fn(), 3 | fetch: jest.fn(state => Promise.resolve(state)), 4 | }; 5 | -------------------------------------------------------------------------------- /assets/cover_rn_offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgommezz/react-native-offline/673a2eb1864ed39194408a07e3a3612a39c89ced/assets/cover_rn_offline.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:metro-react-native-babel-preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p12 6 | *.key 7 | *.mobileprovision 8 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /example/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Platform, StatusBar, StyleSheet, View } from 'react-native'; 3 | import { Ionicons } from '@expo/vector-icons'; 4 | import * as Font from 'expo-font'; 5 | import AppNavigator from './navigation/AppNavigator'; 6 | import DummyNetworkContext from './DummyNetworkContext'; 7 | 8 | const onlineUrl = 'https://www.google.com/'; 9 | const offlineUrl = 'https://www.weifhweopfhwioehfiwoephfpweoifhewifhpewoif.com'; 10 | interface Props { 11 | skipLoadingScreen?: boolean; 12 | } 13 | 14 | interface State { 15 | isLoadingComplete: boolean; 16 | network: { 17 | pingUrl: string; 18 | toggleConnection: () => void; 19 | }; 20 | } 21 | export default class App extends React.Component { 22 | constructor(props: Props) { 23 | super(props); 24 | this.state = { 25 | isLoadingComplete: false, 26 | network: { 27 | pingUrl: onlineUrl, 28 | toggleConnection: this.toggleConnection, 29 | }, 30 | }; 31 | } 32 | 33 | async componentDidMount() { 34 | await Font.loadAsync({ 35 | ...Ionicons.font, 36 | 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'), 37 | }); 38 | this.setState({ isLoadingComplete: true }); 39 | } 40 | 41 | toggleConnection = () => { 42 | this.setState(prevState => ({ 43 | network: { 44 | ...prevState.network, 45 | pingUrl: 46 | prevState.network.pingUrl === onlineUrl ? offlineUrl : onlineUrl, 47 | }, 48 | })); 49 | }; 50 | 51 | render() { 52 | const { isLoadingComplete, network } = this.state; 53 | const { skipLoadingScreen } = this.props; 54 | if (!isLoadingComplete && !skipLoadingScreen) { 55 | return null; 56 | } 57 | return ( 58 | 59 | 60 | {Platform.OS === 'ios' && } 61 | 62 | 63 | 64 | ); 65 | } 66 | } 67 | 68 | const styles = StyleSheet.create({ 69 | container: { 70 | flex: 1, 71 | backgroundColor: '#fff', 72 | }, 73 | }); 74 | -------------------------------------------------------------------------------- /example/DummyNetworkContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface Context { 4 | toggleConnection: () => void; 5 | pingUrl: string; 6 | } 7 | 8 | const DummyNetworkContext = React.createContext({ 9 | pingUrl: 'https://google.com', 10 | toggleConnection: () => {}, 11 | }); 12 | export default DummyNetworkContext; 13 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ## Example app 2 | 3 | This application showcases component utilities, redux, sagas integration and the offline queue with different configurations. You can try it out in Expo by using [this link](https://exp.host/@rgommezz/react-native-offline-example). 4 | 5 | ### Runing it locally 6 | You need to have `expo-cli` installed. 7 | 8 | ```bash 9 | cd example 10 | yarn install 11 | expo start 12 | ``` 13 | 14 | ### Snapshots 15 |
16 | 17 | 18 | 19 |
20 | -------------------------------------------------------------------------------- /example/__tests__/App-test.tsx: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import renderer from 'react-test-renderer'; 4 | import NavigationTestUtils from 'react-navigation/NavigationTestUtils'; 5 | import App from '../App'; 6 | 7 | describe('App snapshot', () => { 8 | jest.useFakeTimers(); 9 | beforeEach(() => { 10 | NavigationTestUtils.resetInternalState(); 11 | }); 12 | 13 | it('renders the loading screen', async () => { 14 | const tree = renderer.create().toJSON(); 15 | expect(tree).toMatchSnapshot(); 16 | }); 17 | 18 | it('renders the root without loading screen', async () => { 19 | const tree = renderer.create().toJSON(); 20 | expect(tree).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "react-native-offline-example", 4 | "slug": "react-native-offline-example", 5 | "description": "Example application that showcases all the different modules that react-native-offline has to offer", 6 | "privacy": "public", 7 | "sdkVersion": "36.0.0", 8 | "platforms": [ 9 | "ios", 10 | "android" 11 | ], 12 | "version": "1.0.0", 13 | "orientation": "portrait", 14 | "icon": "./assets/images/icon.png", 15 | "splash": { 16 | "image": "./assets/images/splash.png", 17 | "resizeMode": "contain", 18 | "backgroundColor": "#ffffff" 19 | }, 20 | "updates": { 21 | "fallbackToCacheTimeout": 0 22 | }, 23 | "assetBundlePatterns": [ 24 | "**/*" 25 | ], 26 | "ios": { 27 | "supportsTablet": true 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgommezz/react-native-offline/673a2eb1864ed39194408a07e3a3612a39c89ced/example/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /example/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgommezz/react-native-offline/673a2eb1864ed39194408a07e3a3612a39c89ced/example/assets/images/icon.png -------------------------------------------------------------------------------- /example/assets/images/redux-saga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgommezz/react-native-offline/673a2eb1864ed39194408a07e3a3612a39c89ced/example/assets/images/redux-saga.png -------------------------------------------------------------------------------- /example/assets/images/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgommezz/react-native-offline/673a2eb1864ed39194408a07e3a3612a39c89ced/example/assets/images/redux.png -------------------------------------------------------------------------------- /example/assets/images/robot-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgommezz/react-native-offline/673a2eb1864ed39194408a07e3a3612a39c89ced/example/assets/images/robot-dev.png -------------------------------------------------------------------------------- /example/assets/images/robot-prod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgommezz/react-native-offline/673a2eb1864ed39194408a07e3a3612a39c89ced/example/assets/images/robot-prod.png -------------------------------------------------------------------------------- /example/assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgommezz/react-native-offline/673a2eb1864ed39194408a07e3a3612a39c89ced/example/assets/images/splash.png -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /example/components/ActionButtons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { 4 | Alert, 5 | View, 6 | StyleSheet, 7 | Text, 8 | TouchableHighlight, 9 | } from 'react-native'; 10 | import { 11 | addOne, 12 | subOne, 13 | other, 14 | cancelOther, 15 | AddOneType, 16 | SubOneType, 17 | OtherType, 18 | CancelOtherType, 19 | } from '../redux/actions'; 20 | 21 | const showInfo = (actionType: string) => () => { 22 | const message = (() => { 23 | switch (actionType) { 24 | case 'ADD_1': 25 | return ( 26 | 'This action adds 1 to the counter state. It is configured to be enqueued if offline ' + 27 | 'and uses a unique id per action, so that we can add to the queue as many as we want.' 28 | ); 29 | case 'SUB_1': 30 | return ( 31 | 'This action subtracts 1 to the counter state. It is configured to be enqueued if offline ' + 32 | 'and uses a unique id per action, so that we can add to the queue as many as we want.' 33 | ); 34 | case 'OTHER': 35 | return ( 36 | "This action does not change the UI state. It's only purpose is to demonstrate that if we dispatch the same " + 37 | 'action to the queue several times, it will replace the existing one and a new instance to the end of the queue. ' + 38 | "It's also dismissable, so that if an action with type CANCEL_OTHER is dispatched, it will be removed from the queue." 39 | ); 40 | case 'CANCEL_OTHER': 41 | return ( 42 | "This action does not change the UI state and it's NOT configured to be added to the offline queue." + 43 | ' If dispatched, it will dismiss actions from the queue with type OTHER.' 44 | ); 45 | default: 46 | return ''; 47 | } 48 | })(); 49 | Alert.alert(actionType, message, [{ text: 'OK', onPress: () => ({}) }], { 50 | cancelable: false, 51 | }); 52 | }; 53 | type Props = { 54 | addOneAction: AddOneType; 55 | subOneAction: SubOneType; 56 | otherAction: OtherType; 57 | cancelOtherAction: CancelOtherType; 58 | }; 59 | 60 | function ActionButtons({ 61 | addOneAction, 62 | subOneAction, 63 | otherAction, 64 | cancelOtherAction, 65 | }: Props) { 66 | return ( 67 | 68 | Actions to dispatch 69 | Long tap on each action for more info 70 | 71 | 76 | ADD_1 77 | 78 | 83 | SUB_1 84 | 85 | 86 | 87 | 92 | OTHER 93 | 94 | 99 | CANCEL_OTHER 100 | 101 | 102 | 103 | ); 104 | } 105 | 106 | export default connect( 107 | null, 108 | { 109 | addOneAction: addOne, 110 | subOneAction: subOne, 111 | otherAction: other, 112 | cancelOtherAction: cancelOther, 113 | }, 114 | )(ActionButtons); 115 | 116 | const styles = StyleSheet.create({ 117 | container: { 118 | flex: 1, 119 | justifyContent: 'center', 120 | }, 121 | title: { 122 | fontSize: 17, 123 | color: 'rgba(96,100,109, 1)', 124 | lineHeight: 24, 125 | textAlign: 'center', 126 | fontWeight: 'bold', 127 | }, 128 | subtitle: { 129 | fontSize: 14, 130 | color: 'rgba(96,100,109, 1)', 131 | textAlign: 'center', 132 | fontStyle: 'italic', 133 | }, 134 | row: { 135 | justifyContent: 'center', 136 | flexDirection: 'row', 137 | marginVertical: 8, 138 | }, 139 | button: { 140 | color: '#388E3C', 141 | marginHorizontal: 8, 142 | fontSize: 18, 143 | }, 144 | }); 145 | -------------------------------------------------------------------------------- /example/components/ConnectionToggler.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, View } from 'react-native'; 3 | import DummyNetworkContext from '../DummyNetworkContext'; 4 | 5 | function ConnectionToggler() { 6 | return ( 7 | 8 | {({ toggleConnection }) => ( 9 | 10 |