7
头图

朋友的网站有个需求:要从 A 域名迁移到 B 域名。所有内容不变,只是更改域名。这个需求不复杂,理论上改下配置然后 301 即可。但这个网站是纯静态网站,用户数据都存在 localStorage 里,所以他希望能够自动帮用户把数据也迁移到新域名。

我们知道,localStorage 是按照域名存储的,B 网站无法访问 A 网站的 localStorage。所以我们就需要一些特殊的手段来实现需求。经过一些调研,我们准备使用 postMessage() 来完成这个需求。

大体的方案如下:

首先,增加 migrate.html 页面。 这个页面不需要其它具体功能,只要侦听 message 事件,并且把 localStorage 传出即可。

<!-- migrate.html -->
<script>
  window.addEventListener('message', function (message) {
    const { origin, source } = message;
    // 验证来源,只接受我们自己的域名发来的请求
    if (origin !== 'https://wordleunlimited.me') return;

    const local = localStorage.getItem('wordle');
    // `source` 是浏览器自动填充的,即调用 `postMessage` 的来源。作为跨域 iframe 里的页面,拿不到外层 window,只能通过这种方式往回传递数据。
    source.postMessage({
      type: 'migrate',
      stored: local,
    }, 'https://wordleunlimited.me');
  });
</script>

然后,在应用里增加 <iframe>,因为我用 Vue3,所以这里也用 Vue 组件的方式处理。

<!-- App.vue -->
<template lang="pug">
migrate-domain(v-if="needMigrate")
</template>

<script setup>
// 我会把迁移的状态持久化,以免反复提示打扰用户
const needMigrate = location.hostname === 'wordleunlimited.me' && !store.state.migrated19;
</script>
<!-- migrate-domain.vue -->
<script lang="ts" setup>
import {ref} from "vue";
// 项目启动得早,还在用 vuex
import {useStore} from "vuex";
import {key} from "@/store";

const store = useStore(key);
const migrateIFrame = ref<HTMLIFrameElement>();

// migrate.html 接到请求后,验证来源,然后会把 localStorage 的数据发回。我们用这个函数接收。
window.addEventListener('message', message => {
  const { origin, data } = message;
  // 同样验证来源
  if (origin !== 'https://mywordle.org' && origin !== 'https://mywordgame.com') return;
  const { type, stored } = data;
  if (type !== 'migrate') return;
  if (stored) {
    // 迁移数据时,需加入特殊标记,用来标记“已迁移”状态
    localStorage.setItem('wordle', stored.replace(/}$/, ', "migrated19": true}'));
    // 很奇怪,直接 reload 可能会迁移失败,所以这里稍微等一下
    setTimeout(() => {
      if (confirm('Data migrated, reload the page?')) {
        location.reload();
      }
    }, 100);
  }
});

// iframe 加载完即执行这段 JS,向 iframe 内的页面传递迁移请求
function onLoad() {
  const contentWindow = migrateIFrame.value?.contentWindow;
  if (contentWindow) {
    contentWindow.postMessage('migrate', 'https://mywordle.org');
  } else {
    console.warn('no content window');
  }
}
</script>

<template lang="pug">
iframe(
  ref="migrateIFrame"
  src="https://mywordle.org/migrate.html"
  frameborder="no"
  width="0"
  height="0"
  @load="onLoad"
)
</template>

<style scoped>
iframe {
  width: 0;
  height: 0;
}
</style>

至此,功能完成。

如此一来,老用户打开网站后,会被跳转到新域名。然后应用 JS 会检查 localStorage 里存储的数据,如果没有迁移过,就会使用 <iframe> 加载老域名下的 migrate.html。等待目标页面加载完成之后,调用 postMessage() 发送迁移请求。接下里,migrate.html 接到请求后,返回之前存储的数据。新域名储存之后,提示刷新。

主要的坑在于 <iframe> 里的页面无法直接跟跨域页面通信,所以需要父页面先找到子页面,发起请求;然后子页面再把数据回传给父页面。其它方面应该就是一般的 API 调用,以及体验性问题。
希望本文对大家有帮助。如果各位对 postMessage() 或者其它相关技术有问题的话,欢迎留言交流。

本文参与了SegmentFault 思否写作挑战赛,欢迎正在阅读的你也加入。

Meathill
22.3k 声望8.6k 粉丝

爱编程,爱旅游,爱吐槽。