Best approach to store http tasks when device offline and call then when goes online?

#1

Hi guys,

I need to have in my app the option of when user tries something (like updating his contact list) and the device has no internet connection I store that action as a task and then when the devices goes online those tasks must be executed (call web service).

I thought about something like this:

  • store the type of method (post/get), the endpoint and the payload (Ionic Store)
  • on my Network Service when goes online walk throughout that list and call those actions.

Any better ideas? How to avoid repeated tasks supposing the user tries several times the same action? Or something like that.

Did anybody ever do such thing?

Here is my (just as an example) my sendPost in my Rest Service:

  public sendPost(action: string, payload: any, showLoading: boolean = true) {
    if (this.isConnected) {
      this.loadingStatus(showLoading);
      return ( < Observable < any >> this.http.post(this.setEndPoint(action), payload, this.requestHeaders()))
        .pipe(
          retryWhen(genericRetryStrategy()),
          tap(() => {
            this.loadingStatus(false);
          }),
          catchError(err => {
            this.loadingStatus(false);
            return throwError(err);
          })
        );
    } else { // OFFLINE
      // This is the task I want to store to be executed later
      let task: ServerTask = new ServerTask('post', action, payload);
      this.help.showToast('You are offline, once you get online this taks will be executed.')
      return this.error$;
    }
  }
#2

Your general design idea sounds solid to me.

This cannot be unilaterally done on the client side. The protocol must be designed to be idempotent (iow, repeated identical actions are harmless). A simple way to do this is to generate a UUID on the client and send it with each response. The server can track which UUIDs it has seen and ignore those which have already been processed.

#3

Thanks for your answer,

I was thinking (later) about having something like this to avoid sending twice the same action:

private addSeverTask(task: ServerTask) {
    this.cacheService.get(TASKS_KEY_NAME).subscribe((tasksCacheData) => {
      if (tasksCacheData === null) {
        // No tasks stored
        console.log('1) No tasks stored yet');
        let newList: ServerTask[] = [task];

        this.cacheService.set(TASKS_KEY_NAME, newList).subscribe((data) => {
          if (data) {
            console.log('2) New task list added');
          }
        })
      } else {
        // Task list aready exists
        console.log('Task list already exist');
        let taskList: ServerTask[] = tasksCacheData.cacheData;
        let index: number = taskList.findIndex(item => item.method == task.method && item.action === task.action);
        if (index > -1) {
          console.log(`3) This task "${task.action}" is already scheduled. Will not cache it.`);
        } else {
          // Add new task to the list
          console.log(`4) This task "${task.action}" is not scheduled yet`);
          taskList.push(task);
          this.cacheService.set(TASKS_KEY_NAME, taskList).subscribe((data) => {
            if (data) {
              console.log('5) Task list updated');
              console.log(taskList);
            }
          })
        }
      }
    })
  }

Then on my onConnect on my Network Service I just check what is scheduled and make the calls.

Any thoughts?

#4

It’s a nice effort, but ultimately pointless. There’s no way that the client can possibly distinguish between the following two scenarios:

  • send goes out, network fails before request gets to server
  • send goes out, request gets to server, network fails before acknowledgement gets back

The only player that can know for certain that a request has been processed is the server, so the duplicate protection has to happen there.

1 Like