Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Offline-friendly #1909

Open
eric-burel opened this issue Feb 19, 2018 · 13 comments
Open

[Feature] Offline-friendly #1909

eric-burel opened this issue Feb 19, 2018 · 13 comments

Comments

@eric-burel
Copy link
Contributor

eric-burel commented Feb 19, 2018

Hi,
I am working on offline features. I know that it sounds weird for a real-time framework to be offline first, but this feature is more and more requested, especially for low connectivity app (mobile user, salesmen etc.). Real-time and offline is actually a good combination: the data loads whenever you are connected with no refresh, and when you lose connection its still there.

The bare minimum is to have a caching mechanism so that data are available on small connectivity losses.

apollo-cache-persist : not good for Vulcan 1.x but good for next versions of Vulcan, apollo-offline : not good

As proposed by @justinr1234, apollo-cache-persist seems to be a good solution, but is meant for Apollo 2.0 only. Is Vulcan meant to include Apollo 2.0 in the versions to come ?

While we are using Apollo-client 1.x, that rely on redux we could use Malpaux/apollo-offline but I did not manage to set it up.

The problem is that the lib does not seem very happy with the existing hydratation process and SSR.
I got some issue about state.offline being unexpected / undefined:

Unexpected keys "offline", "rehydrated" found in preloadedState argument passed to createStore. Expected to find one of the known reducer keys instead: "messages", "apollo". Unexpected keys will be ignored.
// happens because `state.offline` is not defined
Uncaught TypeError: Cannot read property 'outbox' of undefined

redux-persist : good?

It does not seem to crash blatantly. The localStorage is filled.

I also wrote a rehydratation callback for testing purposes, however I am not sure this hook will be actuallly called when I need it to to support offline mode (e.g on any page reload).

addCallback('router.client.rehydrate', ({ store }) => {
  const { persistor } = getRenderContext();
  console.log('rehydrating here', store.getState(), persistor.getState())
})

in the vulcan:core/modules/callbacks.js file. It is called correctly but I can't tell how to actually rehydrate. persistor.getState() looks like this:

{
registry: ["apollo"],
boostraped: false
}

Here is how it is created in vulcan:lib/modules/redux.js:

const persistConfig = {
  key: 'root',
  storage,
}

// create store, and implement reload function
export const configureStore = (reducers, initialState = {}, middlewares) => {
  let getReducers;
  if (typeof reducers === 'function') {
    getReducers = reducers;
    reducers = getReducers();
  }
  reducers = typeof reducers === 'object' ? combineReducers(reducers) : reducers;
  if (Meteor.isClient) reducers = persistReducer(persistConfig, reducers)

  middlewares = Array.isArray(middlewares) ? middlewares : [middlewares];

  const store = createStore(
    // reducers
    reducers,
    // initial state
    initialState,
    // middlewares
    compose(
      applyMiddleware(...middlewares),
      typeof window !== 'undefined' && window.devToolsExtension ? window.devToolsExtension() : f => f,
    ),
  );
  const persistor = persistStore(store)

  store.reload = function reload(options = {}) {
    if (typeof options.reducers === 'function') {
      getReducers = options.reducers;
      options.reducers = undefined;
    }
    if (!options.reducers && getReducers) {
      options.reducers = getReducers();
    }
    if (options.reducers) {
      reducers = typeof options.reducers === 'object' ? combineReducers(options.reducers) : options.reducers;
    }

    if (Meteor.isClient) reducers = persistReducer(persistConfig, reducers)
    this.replaceReducer(reducers);
    return store;
  };

  if (Meteor.isServer) return store
  return { store, persistor };
};

Here again I differentiate server and client, since I don't want persistance on the client. I changed the getRenderContext and routing.jsx to match this new behaviour (simply get {store, persistor} instead of just the store).

I am not sure whether redux-persist will automatically hydrate or wait for me to do some actions. I am not even sure it actually works, because I have no "true" offline mode, the app is not cached so if reload it while offline it simply crash.
But it seems to work right now, so I think this is the way to go at first.

forms : maybe

Did not test it yet but my first idea is simply to disable the SmartForm whenever the app goes offline, and tell the user to wait for the app to go online. Same for forms-upload.

We could also cache the data in a queue and send the form when the app is reconnected but I am not fond of this pattern. The user might think he sent the data while it didn't and close the app before the reconnection happens, thus losing the form data he anted to send.

I propose to track offline related feature here in one place. This might not need to actual change in Vulcan, but adding offline support could be a recipee in the doc.

@SachaG
Copy link
Contributor

SachaG commented Feb 20, 2018

Whatever solution we choose, we should definitely pick one that works with Apollo 2.x, since we're going to upgrade at some point.

Ideally every query/mutation would be cached locally until it can be synced back to the server, which would mean we wouldn't need to disable forms. But I don't know if that's supported by Apollo's offline solutions?

@justinr1234
Copy link
Member

Whatever is done should follow the lead of AppSync’s architecture as they used Apollo 2 and have built a paid product around it:

https://dev-blog.apollodata.com/aws-appsync-powered-by-apollo-df61eb706183

@SachaG
Copy link
Contributor

SachaG commented Feb 20, 2018

I'm in touch with the AppSync people so I could ask them if you have any questions about how they handle offline usage.

@eric-burel
Copy link
Contributor Author

Nice, yes if you plan to update to Apollo 2.x quickly the redux-persist way will be soon obsolete, at least people that are maintaining legacy apps will have some feedback about this.

To serve the app itself Meteor rely on their custom AppCache package or so I understood, since Service Workers are not yet widespread. I guess this would also be the correct solution for Vulcan.

@justinr1234
Copy link
Member

You need a service worker for offline loading

@jacobpdq
Copy link

Bit of research:

Gatsby uses sw-precache
And there are year-old threads for meteor here

@Discordius
Copy link
Contributor

I've looked into this a bunch, and in general it seems that we would want to go the way of service workers and generally making Vulcan by default a fully progressive web app.

@stale
Copy link

stale bot commented Nov 23, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Nov 23, 2018
@eric-burel
Copy link
Contributor Author

Bad bot, this is still relevant. We could take care of this after the apollo2 update.

@stale stale bot removed the stale label Nov 23, 2018
@Zenoo
Copy link

Zenoo commented Oct 3, 2019

Is there any progress on this?

@eric-burel
Copy link
Contributor Author

Not yet, I don't know how offline management evolved with Apollo2 and we are still focused on improving existing features. We'd be very glad to have external contributions on this, I don't think it's complicated but it requires to list existing solutions.

@Zenoo
Copy link

Zenoo commented Oct 4, 2019

Here's an issue referencing offline support: apollographql/apollo-feature-requests#11

They're recommending Offix: https://github.com/aerogear/offix/

@justinr1234
Copy link
Member

Offline is very complicated. This would take a very intimate knowledge of Vulcan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants