エラー処理ガイド

エラークラス階層

Diagram 0

インポート

import { OpenloopError, ApduError, TransportError } from '@openloop/sdk-core'

ApduError

デバイスがエラーのステータスワード(0x9000 以外)を返した場合にスローされます。

class ApduError extends OpenloopError {
  readonly statusWord: number  // 例: 0x6985
}

APDU ステータスワード表

ステータスワード 定数名 説明 よくある原因
0x9000 — 成功 —
0x6985 Conditions not satisfied 拒否、またはデバイスが使用中 利用者が拒否ボタンを押した/別の確認画面が出ている最中に要求が来た(本体 v1.1.5 以降は即答)
0x6a80 Invalid data 無効なデータ 不正な BIP44 パスやトランザクション
0x6a82 App not found アプリが見つからない デバイスで対応アプリが開いていない
0x6d00 Instruction not supported 命令未サポート デバイスのファームウェアバージョンが古い
0x6e00 CLA not supported CLA 未サポート 不正なクラスバイト
0x6f00 Internal error 内部エラー デバイス内部の予期しないエラー
0x61XX More data 続きデータあり PSBT 取得時の中間レスポンス(エラーではない)

使用例

import { ApduError } from '@openloop/sdk-core'

try {
  const sig = await eth.signPersonalMessage(DEFAULT_ETH_PATH, 'Hello')
} catch (err) {
  if (err instanceof ApduError) {
    if (err.statusWord === 0x6985) {
      console.log('デバイスで拒否されたか、デバイスが使用中です。画面を確認してもう一度お試しください')
    } else {
      console.log(`APDU エラー: 0x${err.statusWord.toString(16)}`)
    }
  }
}

TransportError

Transport レイヤー(USB/BLE/WebSocket 等)で通信エラーが発生した場合にスローされます。

class TransportError extends OpenloopError {
  readonly code: TransportErrorCode   // v0.6.0 で追加
  // message には英語の詳細が入る(表示用ではなくログ用)
}

code で分岐する(v0.6.0 で追加)

message は英語の自由文なので、文字列で分岐しないでください。code を見てください。

code 意味 対処法
not-supported この実行環境がそもそもデバイスと話せない(WebHID / Web Bluetooth が無い) 対応ブラウザを案内する
not-found 一致するデバイスが無い、または選択画面がキャンセルされた もう一度選んでもらう
not-connected 接続が開いていない(未接続、または既に閉じた) open() してから使う
timeout デバイスが時間内に応答しなかった デバイスの画面を確認(承認待ちかもしれない)
disconnected 使用中にリンクが切れた 再接続を試みる
io それ以外(フレーミング、書き込み失敗、想定外の応答) ログに message を残す

code の既定値は io です。将来 code が増えても、default で受ければ壊れません。

デバイスが返したエラーは ApduError

v0.6.0 で、BLE / Safari / USB の 3 transport が status word を捨てずに ApduError を投げるようになりました。 以前はこれらが TransportError になり、statusWord で分岐できませんでした。 デバイス由来のエラー(拒否・条件不成立など)は ApduError 側で受けてください。


エラーハンドリングパターン

基本パターン

import { OpenloopError, ApduError, TransportError } from '@openloop/sdk-core'

try {
  const { address } = await eth.getAddress(DEFAULT_ETH_PATH)
} catch (err) {
  if (err instanceof ApduError) {
    // デバイスからのエラー応答
    switch (err.statusWord) {
      case 0x6985:
        showMessage('デバイスで拒否されたか、デバイスが使用中です。画面を確認してもう一度お試しください')
        break
      case 0x6a80:
        showMessage('無効なデータです')
        break
      default:
        showMessage(`デバイスエラー: ${err.message}`)
    }
  } else if (err instanceof TransportError) {
    // 通信エラー — message ではなく code で分岐する
    switch (err.code) {
      case 'not-supported':
        showMessage('このブラウザは対応していません')
        break
      case 'not-found':
        showMessage('デバイスが見つかりませんでした')
        break
      case 'timeout':
        showMessage('デバイスが応答しません。画面をご確認ください。')
        break
      case 'disconnected':
      case 'not-connected':
        showMessage('接続が切れました。再接続してください。')
        break
      default:
        showMessage(`通信エラー: ${err.message}`)
    }
  } else if (err instanceof OpenloopError) {
    // その他の SDK エラー
    showMessage(`エラー: ${err.message}`)
  } else {
    // 予期しないエラー
    throw err
  }
}

再接続付きリトライパターン

async function withRetry<T>(
  fn: () => Promise<T>,
  reconnect: () => Promise<void>,
  maxRetries: number = 1
): Promise<T> {
  for (let i = 0; i <= maxRetries; i++) {
    try {
      return await fn()
    } catch (err) {
      if (err instanceof TransportError && i < maxRetries) {
        await reconnect()
        continue
      }
      throw err
    }
  }
  throw new Error('Unreachable')
}

// 使用例
const { address } = await withRetry(
  () => eth.getAddress(DEFAULT_ETH_PATH),
  async () => {
    transport = await WebHidTransport.reconnect()
    eth = new EthereumApp(transport!)
  }
)

拒否・使用中の判定

function isRejectedOrBusy(err: unknown): boolean {
  return err instanceof ApduError && err.statusWord === 0x6985
}

try {
  const sig = await eth.signTransaction(DEFAULT_ETH_PATH, rawTx)
} catch (err) {
  if (isRejectedOrBusy(err)) {
    // 拒否か使用中 — 画面を確かめてもう一度
    showMessage('デバイスで拒否されたか、デバイスが使用中です。画面を確認してもう一度お試しください')
    return
  }
  // その他のエラーは表示
  showError(err)
}

次のステップ