Heads-up: @forge/bridge view.createHistory() typings are misleading

For those who wish to play with the History API in Forge apps, please note that the @forge/bridge view.createHistory() typings are misleading.

According to the typings, the createHistory() method will return a vanilla History object:

createHistory: () => Promise<import("history").History>;

That import leads to the following package, as defined in package.json:

"@types/history": "^4.7.11",

As per the definition of history in that package, it is defined as:

export interface History<HistoryLocationState = LocationState> {
    length: number;
    action: Action;
    location: Location<HistoryLocationState>;
    push(location: Path | LocationDescriptor<HistoryLocationState>, state?: HistoryLocationState): void;
    replace(location: Path | LocationDescriptor<HistoryLocationState>, state?: HistoryLocationState): void;
    go(n: number): void;
    goBack(): void;
    goForward(): void;
    block(prompt?: boolean | string | TransitionPromptHook<HistoryLocationState>): UnregisterCallback;
    listen(listener: LocationListener<HistoryLocationState>): UnregisterCallback;
    createHref(location: LocationDescriptorObject<HistoryLocationState>): Href;
}

However, the actual implementation of @forge/bridge does not follow this specification, specifically for createHref(location: LocationDescriptorObject<HistoryLocationState>): Href.

When calling createHref() on the History object created by @forge/bridge, the method returns a Promise.

This is probably due to the fact that the implementation is based on messaging between parent window and Forge iframe, but the fact remains that this means that manipulating History is not a synchronous operation in Forge. And that has ramifications for apps that rely on it.

So Atlassian, either please change the History operations to be synchronous, or adjust the bindings and make sure that you are not pretending to follow the official History object typings when you’re not.

I reported this in FRGE-1436, but it’s been in the “Needs Triage” status for over two years. :eyes:

Hi @remie , @klaussner thanks for the detailed write-up, and apologies that FRGE-1436 sat in triage for so long :folded_hands:

You’re right, and the typings are wrong here. The object returned by view.createHistory() lives in the host product (Jira, Confluence, etc.) and is accessed from your iframe over the Custom UI bridge, which is postMessage-based. That means every method on it is inherently asynchronous - push, replace, go, listen, and anything else you call on it returns a Promise. Making those operations synchronous isn’t possible given how the bridge works, so we’ll fix the types and the documentation.

I’ll provide updates on the FRGE ticket.