Skip to content

Synchronize a workspace

This recipe synchronizes one dossier of your software into Ubikap, then waits for the result. It reuses the client and the makeRpc transport of Getting Started.

const { token } = await authenticationService.LogIn({
officeScope: { target: { office: { externalId: officeId } } },
userUuid: SYNCHRO_USER_UUID,
userCode: currentTotpCode(),
});
const synchronizationService = new SynchronizationServiceClientImpl(makeRpc(token));

The token lasts 30 minutes: log in at the start of each run instead of reusing an older token. See Authentication.

const source = { credential: { externalId: userId } };
const { synchronizationId } = await synchronizationService.PostAsyncTasks({
tasks: [
{
synchronizeOfficeUsers: {
target: {},
source,
},
},
{
synchronizeFullWorkspace: {
target: { workspace: { externalId: dossierId } },
source,
},
},
],
});

userId is the user of your software on whose behalf the data is read, typically the one who changed the dossier. Store synchronizationId: it is the only handle on the synchronization.

synchronizeOfficeUsers brings the office’s users up to date first: the dossier’s accesses name its manager and collaborators, who must exist in Ubikap, or synchronizeWorkspaceAccesses fails. It always runs before the workspace tasks, whatever their order in tasks, and at most once a day per office: when the users were already synchronized today, it ends done at once, so posting it with every synchronization costs nothing.

Several dossiers can go in the same call, one task each. A task type that is not implemented makes the whole call fail with INVALID_ARGUMENT, and nothing is queued: see Task types.

const POLL_INTERVAL_MS = 5_000;
const POLL_TIMEOUT_MS = 10 * 60_000;
const waitForSynchronization = async (synchronizationId: string) => {
const giveUpAt = Date.now() + POLL_TIMEOUT_MS;
while (Date.now() < giveUpAt) {
const { tasks, stats } = await synchronizationService.GetSynchroState({ synchronizationId });
if (stats && stats.pendingCount === 0 && stats.runningCount === 0) {
return { tasks, failedCount: stats.failedCount };
}
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
}
throw new Error(`Synchronization ${synchronizationId} still running`);
};
const { tasks, failedCount } = await waitForSynchronization(synchronizationId);
if (failedCount > 0) {
console.warn(tasks.filter((task) => task.state === 'failed').map((task) => task.type));
}

Over REST, the counters are JSON strings: compare stats.pendingCount === "0", or convert them first.

A synchronization with failed tasks is retried automatically, so failed is the state reached after the retries. Its tasks report which part failed, not why: check that the dossier, its users and the credential exist in your software, then post a new synchronization.

If a synchronization keeps failing, or stays pending or running for longer than your timeout, contact Ubikap support with its synchronizationId, the office’s externalId and the dossier’s externalId: we can see why a task failed, and run the synchronization again.

To display when a dossier was last synchronized, without keeping the ids:

const { lastSuccessful, failed } = await synchronizationService.GetGlobalSynchroState({
filter: { workspace: { externalId: dossierId } },
});
const lastSynchronizedAt = lastSuccessful?.date; // When that synchronization was posted
const needsRetry = (failed?.synchronizationIds.length ?? 0) > 0;
  • A fresh LogIn for each run, one token per office: a token lasts 30 minutes.
  • synchronizeOfficeUsers posted with the workspace tasks, so that the dossier’s users exist in Ubikap.
  • externalIds are ids of your software, never Ubikap keys.
  • Poll GetSynchroState at a reasonable pace (every few seconds), with an overall timeout.
  • A failed task has already been retried: fix the cause before posting again.