You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React集成SharePoint文件选择器异常:无文件加载及报错求助

SharePoint文件选择器集成React应用异常排查

异常现象

  • 文件选择器弹窗可正常打开,但无法加载文件列表
  • 偶发两类错误:
    • PrefetchFailure (3001000,invalid_client)
    • Initialization timed out after 40000ms

相关实现代码

const sharePointBaseUrl = 'https://tenant.sharepoint.com';

// 注意:原函数未声明async,内部使用await会报错;且存在accessToken变量重复声明
const openSharepoint = (accessToken, options, baseUrl) => {
  let port = null;
  const win = window.open('', 'Picker', 'width=1080,height=680');

  const queryString = new URLSearchParams({
    filePicker: JSON.stringify(options),
    locale: 'en-us',
  });

  // 此处错误:函数未async,不能用await;且参数已传入accessToken,重复声明会覆盖
  const accessToken = await getToken(baseUrl);

  const url = `${baseUrl}/_layouts/15/FilePicker.aspx?${queryString}`;

  const form = win.document.createElement('form');
  form.setAttribute('action', url);
  form.setAttribute('method', 'POST');
  const tokenInput = win.document.createElement('input');

  tokenInput.setAttribute('type', 'hidden');
  tokenInput.setAttribute('name', 'access_token');
  tokenInput.setAttribute('value', accessToken);

  form.appendChild(tokenInput);
  win.document.body.append(form);
  form.submit();

  window.addEventListener('message', (event) => {
    if (event.source && event.source === win) {
      const message = event.data;

      if (message.type === 'initialize' && message.channelId === options.messaging.channelId) {
        port = event.ports[0];
        port.addEventListener('message', messageListener);
        port.start();
        port.postMessage({ type: 'activate' });
      }
    }
  });
};

// 注意:原函数存在语法错误,console.log缺少反引号
async function messageListener(message) { 
  switch (message.data.type) { 
    case 'notification': 
      // 此处错误:模板字符串未用反引号包裹
      console.log(notification: ${message.data}); 
      console.log(message); 
      break;

    case 'command':
      port.postMessage({ type: 'acknowledge', id: message.data.id });
      const command = message.data.data;

      switch (command.command) {
        case 'authenticate':
          const token = accessToken;
          if (typeof token !== 'undefined' && token !== null) {
            port.postMessage({
              type: 'result',
              id: message.data.id,
              data: { result: 'token', token },
            });
          } else {
            console.error(`Could not get auth token for command: ${JSON.stringify(command)}`);
          }
          break;

        case 'close':
          win.close();
          break;

        case 'pick':
          console.log(`Picked: ${JSON.stringify(command)}`);
          document.getElementById('pickedFiles').innerHTML = `<pre>${JSON.stringify(command, null, 2)}</pre>`;
          port.postMessage({ type: 'result', id: message.data.id, data: { result: 'success' } });
          win.close();
          break;

        default:
          console.warn(`Unsupported command: ${JSON.stringify(command)}`, 2);
          port.postMessage({
            result: 'error',
            error: { code: 'unsupportedCommand', message: command.command },
            isExpected: true,
          });
          break;
      }
      break;
  }
}

const openSharePointPopUp = () => { 
  getAccessToken('sharepoint').then((token) => {
    const channelId = uuidv4(); 
    const options = {
      sdk: '8.0',
      entry: { sharePoint: { files: {} } },
      authentication: {},
      messaging: { origin: 'http://localhost:3010', channelId },
      typesAndSources: {
        mode: 'all',
        filters: ['.aspx'],
        pivots: { recent: false, oneDrive: false },
      },
    };
    openSharepoint(token, options, sharePointBaseUrl);
  });
};

const sharePointApp = () => { 
  checkIntegration('sharepoint').then((res) => { 
    const authToken = Storage.getData('jwt_token');
    if (!res) {
      const payload = { provider: 'sharepoint', token: authToken };
      integrate(payload).then((res) => window.open(res.data.redirect_uri, '_blank'));
    }
    openSharePointPopUp();
    return res;
  });
};

问题分析及解决方案

1. 函数语法错误(核心问题)

  • 问题:openSharepoint函数未声明为async,但内部使用了await getToken(baseUrl),直接导致运行时错误;同时函数参数已传入accessToken,内部重复声明会覆盖参数值,导致令牌失效。
  • 修复:
    const openSharepoint = async (accessToken, options, baseUrl) => {
      let port = null;
      const win = window.open('', 'Picker', 'width=1080,height=680');
    
      const queryString = new URLSearchParams({
        filePicker: JSON.stringify(options),
        locale: 'en-us',
      });
    
      // 若需要重新获取令牌,改用其他变量名,避免覆盖参数
      // const freshToken = await getToken(baseUrl);
      // 使用freshToken替代后续的accessToken
    
      const url = `${baseUrl}/_layouts/15/FilePicker.aspx?${queryString}`;
      // ... 剩余代码保持不变
    }
    

2. 令牌有效性问题(invalid_client报错)

  • 问题:invalid_client通常是以下原因:
    • 令牌的受众(aud字段)不是https://tenant.sharepoint.com
    • 应用注册的客户端ID/密钥配置错误
    • 令牌已过期或未正确获取到SharePoint权限
  • 验证:解析令牌确认aud字段匹配SharePoint站点URL,exp字段未过期。
  • 修复:确保获取令牌时指定的资源是https://tenant.sharepoint.com,应用注册已添加Files.Read.All等必要权限并完成授权。

3. 初始化超时问题

  • 问题:Initialization timed out通常是消息通道未正确建立:
    • messageListener中的语法错误(如未闭合的模板字符串)导致监听函数失效,无法响应authenticate命令
    • messaging.origin配置与当前页面origin不一致(需完全匹配协议、域名、端口)
    • activate消息发送不及时或端口未正确启动
  • 修复:
    • 修正messageListener中的语法错误:console.log(notification: ${message.data});
    • 确保options.messaging.origin与当前页面地址完全一致(本地开发注意是http还是https)
    • 检查port.start()调用是否在添加监听之后,确保activate消息能被正确接收

4. 跨域配置问题

  • 问题:本地开发地址http://localhost:3010未被添加到SharePoint应用注册的允许源列表中,导致postMessage跨域被拦截。
  • 修复:在Azure AD应用注册的“身份验证”页面,添加http://localhost:3010到允许的源列表。

内容的提问来源于stack exchange,提问作者piyush0408

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 06:54:57