import { OpenloopError, ApduError, TransportError } from '@openloop/sdk-core'デバイスがエラーのステータスワード(0x9000
以外)を返した場合にスローされます。
class ApduError extends OpenloopError {
readonly statusWord: number // 例: 0x6985
}| ステータスワード | 定数名 | 説明 | よくある原因 |
|---|---|---|---|
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)}`)
}
}
}Transport レイヤー(USB/BLE/WebSocket 等)で通信エラーが発生した場合にスローされます。
class TransportError extends OpenloopError {
readonly code: TransportErrorCode // v0.6.0 で追加
// message には英語の詳細が入る(表示用ではなくログ用)
}message
は英語の自由文なので、文字列で分岐しないでください。code
を見てください。
code |
意味 | 対処法 |
|---|---|---|
not-supported |
この実行環境がそもそもデバイスと話せない(WebHID / Web Bluetooth が無い) | 対応ブラウザを案内する |
not-found |
一致するデバイスが無い、または選択画面がキャンセルされた | もう一度選んでもらう |
not-connected |
接続が開いていない(未接続、または既に閉じた) | open() してから使う |
timeout |
デバイスが時間内に応答しなかった | デバイスの画面を確認(承認待ちかもしれない) |
disconnected |
使用中にリンクが切れた | 再接続を試みる |
io |
それ以外(フレーミング、書き込み失敗、想定外の応答) | ログに message を残す |
codeの既定値はioです。将来codeが増えても、defaultで受ければ壊れません。
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)
}