javascript - Transfer LocalStorage data to a new domain, despite limited support for Storage Access API - Stack Overflow

admin2025-05-02  0

I've had a website hosted on Github Pages without a custom domain (e.g. auroratide.github.io/my-website), and I now want to move it to a custom domain (e.g. my-website). The website saves data into local storage.

my-website cannot access the local storage data from github.io, so I need to find a way to migrate the data so people don't suddenly lose everything.

I thought I was being clever by creating another github pages site, auroratide.github.io/storage-migration, whose only purpose is to be iframed by the new domain and postMessage its data to the parent window. To my dismay, an iframed website cannot access its own local storage due to State Partitioning, in other words storage is "double-keyed" against both the top domain and the iframed domain.

I then attempted to leverage the Storage Access API to request access from the iframe, and that works... in Chrome, and is unsupported in every other browser. Silly me for not checking before implementing an entire solution.


Without support from the Storage Access API, is there any way to migrate local storage to a new domain? Even though this exact question has been asked many times over the years, I'm definitely getting lost in the forest of old-knowledge vs new-knowledge.

If it's useful at all, this is the code I'm currently using in my iframed website:

function postMessage(payload) {
    parent.postMessage(payload, NEW_DOMAIN)
}

function transferStorage(storage) {
    postMessage(Object.entries(storage))
}

async function hasStorageAccess() {
    if (!document.requestStorageAccess) return true
    if (await document.hasStorageAccess()) return true

    try {
        const permission = await navigator.permissions.query({ name: "storage-access" })

        if (permission.state === "granted") {
            await document.requestStorageAccess()
            return true
        } else if (permission.state === "prompt") {
            return false
        } else if (permission.state === "denied") {
            return false
        }
    } catch (error) {
        console.warn(error)
        return false
    }

    return false
}

async function manuallyTransfer() {
    /* Imagine code here that toggles button and loader visibilities */

    const hasAccess = await hasStorageAccess()
    if (!hasAccess) {
        try {
            await document.requestStorageAccess()
        } catch (error) {
            postMessage("failed")
            return
        }
    }

    const handle = await document.requestStorageAccess({ localStorage: true })
    transferStorage(handle.localStorage)
}

async function automaticallyTransfer() {
    const hasAccess = await hasStorageAccess()
    if (hasAccess) {
        if (document.requestStorageAccess) {
            const handle = await document.requestStorageAccess({ localStorage: true })
            transferStorage(handle.localStorage)
        } else {
            transferStorage(localStorage)
        }
    } else {
        console.log("Requires manual input to migrate data.")
        const button = document.querySelector("#transfer-button")
        button.addEventListener("click", manuallyTransfer)
        button.removeAttribute("hidden")
        postMessage("manual")
    }
}

if (parent != null) {
    automaticallyTransfer()
}

I've had a website hosted on Github Pages without a custom domain (e.g. auroratide.github.io/my-website), and I now want to move it to a custom domain (e.g. my-website.com). The website saves data into local storage.

my-website.com cannot access the local storage data from github.io, so I need to find a way to migrate the data so people don't suddenly lose everything.

I thought I was being clever by creating another github pages site, auroratide.github.io/storage-migration, whose only purpose is to be iframed by the new domain and postMessage its data to the parent window. To my dismay, an iframed website cannot access its own local storage due to State Partitioning, in other words storage is "double-keyed" against both the top domain and the iframed domain.

I then attempted to leverage the Storage Access API to request access from the iframe, and that works... in Chrome, and is unsupported in every other browser. Silly me for not checking before implementing an entire solution.


Without support from the Storage Access API, is there any way to migrate local storage to a new domain? Even though this exact question has been asked many times over the years, I'm definitely getting lost in the forest of old-knowledge vs new-knowledge.

If it's useful at all, this is the code I'm currently using in my iframed website:

function postMessage(payload) {
    parent.postMessage(payload, NEW_DOMAIN)
}

function transferStorage(storage) {
    postMessage(Object.entries(storage))
}

async function hasStorageAccess() {
    if (!document.requestStorageAccess) return true
    if (await document.hasStorageAccess()) return true

    try {
        const permission = await navigator.permissions.query({ name: "storage-access" })

        if (permission.state === "granted") {
            await document.requestStorageAccess()
            return true
        } else if (permission.state === "prompt") {
            return false
        } else if (permission.state === "denied") {
            return false
        }
    } catch (error) {
        console.warn(error)
        return false
    }

    return false
}

async function manuallyTransfer() {
    /* Imagine code here that toggles button and loader visibilities */

    const hasAccess = await hasStorageAccess()
    if (!hasAccess) {
        try {
            await document.requestStorageAccess()
        } catch (error) {
            postMessage("failed")
            return
        }
    }

    const handle = await document.requestStorageAccess({ localStorage: true })
    transferStorage(handle.localStorage)
}

async function automaticallyTransfer() {
    const hasAccess = await hasStorageAccess()
    if (hasAccess) {
        if (document.requestStorageAccess) {
            const handle = await document.requestStorageAccess({ localStorage: true })
            transferStorage(handle.localStorage)
        } else {
            transferStorage(localStorage)
        }
    } else {
        console.log("Requires manual input to migrate data.")
        const button = document.querySelector("#transfer-button")
        button.addEventListener("click", manuallyTransfer)
        button.removeAttribute("hidden")
        postMessage("manual")
    }
}

if (parent != null) {
    automaticallyTransfer()
}
Share Improve this question asked Jan 1 at 23:12 AuroratideAuroratide 2,63213 silver badges21 bronze badges 3
  • 1 is it possible to add button "import" on new-domain that opens a popup window of old-domain/export.html which transmits its localStorage to new-domain and then closes it self? – IT goldman Commented Jan 1 at 23:54
  • Since I already have to introduce a button for requestStorageAccess to work fully, a button for a pop up window would be similar UX. The docs suggest using a popup only grants access for 30 days for compatibility reasons, but since this is a one time migration that's theoretically fine... when I have time I'll give it a try for sure! – Auroratide Commented Jan 2 at 1:04
  • One option could be to have a button to copy localStorage to the clipboard, and have another button to paste, or similar by downloading to a json file and uploading it, but that's annoying to users. Depending on how much data you need to transfer, it might be possible to use query parameters to set the data on the 2nd site – Samathingamajig Commented Jan 4 at 0:13
Add a comment  | 

1 Answer 1

Reset to default 1

In the end I decided on using a user-initiated popup as suggested by IT goldman. Since a popup window is not constrained by state partitioning in the same way an iframe is, it's able to postMessage its local storage contents to the opener.

window.open(POPUP_URL, "datatransfer", "popup")
function postMessage(payload) {
    opener.postMessage(payload, TARGET_DOMAIN)
}

function transferStorage(storage) {
    postMessage(Object.entries(storage))
}

async function automaticallyTransfer() {
    try {
        transferStorage(localStorage)
    } catch (e) {
        console.error(e)
        postMessage("failed")
    } finally {
        window.close()
    }
}

if (opener != null) {
    automaticallyTransfer()
}
转载请注明原文地址:http://anycun.com/QandA/1746139607a92124.html