IOS SDK · v0.1
iOS SDK
A Swift package with no third-party dependencies and the same surface as the Android SDK: the cloud path (authorization, the token store, recordings, insights, transcription and webhook verification) and a direct Bluetooth connection to the card: scan, pair, start and stop recording, card status and events, audio export, live audio, smart wake, voiceprint enrolment, firmware updates, and LED and haptic feedback.
零第三方依賴的 Swift 套件,能力面與 Android SDK 一致:雲通路(授權、令牌存儲、錄音、洞察、轉寫與 webhook 校驗),以及與卡片的藍牙直連:掃描、配對、起停錄音、卡片狀態與事件、音頻導出、實時音頻、智能喚醒、聲紋註冊、 固件升級,以及 LED 與振動反饋。
| Platform | Status | Notes |
|---|---|---|
| iOS | AVAILABLE | iOS 15 and later, Swift 5.9. Swift Package or XCFramework. |
| BLE direct connect | AVAILABLE | Scan, pair, start/stop capture, card events, audio export, Wi-Fi fast transfer. |
| Live audio | AVAILABLE | Hold the card's button and speak; audio streams into your foreground app. |
| Device management | AVAILABLE | Smart wake, card name, voiceprint enrolment, firmware updates, delete and unbind with on-card confirmation. |
| LED and haptics | AVAILABLE | LED per card state, vibration per card event, saved on the card. |
| 平台 | 狀態 | 說明 |
|---|---|---|
| iOS | 可用 | iOS 15 及以上,Swift 5.9。Swift Package 或 XCFramework。 |
| BLE 直連 | 可用 | 掃描、配對、起停錄音、卡片事件、音頻導出、Wi-Fi 快傳。 |
| 實時音頻 | 可用 | 按住卡片按鍵說話,音頻實時流入你的前台 App。 |
| 設備管理 | 可用 | 智能喚醒、卡片名稱、聲紋註冊、固件升級,刪除與解綁需在卡上確認。 |
| LED 與振動 | 可用 | 按卡片狀態設置 LED、按卡片事件設置振動,保存在卡片上。 |
Install
Add the VysloOpen Swift package to your target, or drop in VysloOpen.xcframework; ask support@vyslo.ai for the current release. It uses platform frameworks only: Foundation, Security, CoreBluetooth, NetworkExtension and AudioToolbox.
把 VysloOpen Swift 套件加到你的 target,或直接放入 VysloOpen.xcframework;寫信到 support@vyslo.ai 索取當前版本。只用平台自帶框架:Foundation、Security、CoreBluetooth、NetworkExtension 與 AudioToolbox。
import VysloOpen
// appId comes from POST /open/apps — see the Quickstart.
let vyslo = VysloOpen(appId: 42)
// Optional base URL for a self-hosted or staging instance:
// VysloOpen(appId: 42, baseURL: URL(string: "https://your-instance/api/v1/app")!)
vyslo.isAuthorized // Bool — is a token stored?
vyslo.token // String? — the raw capability token
The token is kept in the Keychain (service ai.vyslo.opensdk), so it survives restarts. Every network and Bluetooth call is async; call it from any task.
令牌保存在 Keychain(service ai.vyslo.opensdk),重啟後仍在。所有網絡與藍牙調用都是 async,在任意 Task 裡調用即可。
Info.plist and capabilities
<!-- Open the VYSLO app for consent, and receive the callback -->
<key>LSApplicationQueriesSchemes</key>
<array><string>vyslo</string></array>
<key>CFBundleURLTypes</key>
<array><dict>
<key>CFBundleURLSchemes</key><array><string>notesbot</string></array>
</dict></array>
<!-- Bluetooth to the card, and keep a connected card alive in the background -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Connect to your VYSLO card.</string>
<key>UIBackgroundModes</key>
<array><string>bluetooth-central</string></array>
<!-- Fast transfer over the card's Wi-Fi hotspot -->
<key>NSLocalNetworkUsageDescription</key>
<string>Copy recordings from your VYSLO card over its Wi-Fi.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Confirm that this iPhone joined the card's Wi-Fi.</string>
For fast transfer, also turn on the Hotspot Configuration capability for your app ID in Xcode.
要用 Wi-Fi 快傳,還需在 Xcode 裡為你的 App ID 打開 Hotspot Configuration 能力。
Authorization
// 1. Send the user to the VYSLO app's consent screen.
let url = vyslo.authorizeURL(
scopes: [.recordingsRead, .recordingsAudio],
callbackURI: "notesbot://vyslo-auth"
)
UIApplication.shared.open(url) { opened in
if !opened {
// VYSLO app not installed — send them to install it.
}
}
// 2. Handle the callback (SwiftUI shown; UIKit: scene(_:openURLContexts:)).
.onOpenURL { url in
if vyslo.handleAuthorizationCallback(url) {
// Token stored. Carry on.
} else {
// The user denied, or the callback carried no token.
}
}
Deep link contract
authorizeURL builds this; document it if you need to trigger authorization from
somewhere the SDK cannot reach, such as a web page or another framework.
authorizeURL 構造的就是下面這條。如果你要從 SDK 碰不到的地方(網頁、其他框架)發起授權,
就按它拼。
vyslo://open/authorize
?appId=<your app id>
&scopes=<scope bitmap>
&callback=<url-encoded callback uri>
# approved -> <callback>?vyslo_token=<capability token>
# denied -> <callback>?vyslo_error=denied
callback has to match an entry in your app's callback allowlist — exactly, or with
?/# and your own parameters appended, so notesbot://vyslo-auth
covers notesbot://vyslo-auth?state=abc. The server checks this before issuing, so
nobody can borrow your appId and have the token delivered somewhere else. Register
entries with callbackUris at creation or
POST /open/apps/{appId}/callbacks later.
callback 必須命中你應用的回跳白名單——完全相等,或在登記項後追加
?/# 及你自己的參數,所以登記 notesbot://vyslo-auth
可命中 notesbot://vyslo-auth?state=abc。服務端在簽發前校驗,別人無法冒用你的
appId 把令牌送到別處。建立應用時用 callbackUris 登記,或之後調
POST /open/apps/{appId}/callbacks 更新。
Reading data
let rec: VysloRecording = try await vyslo.fetchRecording(recordingID)
rec.recordingID
rec.title
rec.segments // [VysloRecording.Segment] (speaker, text, timestamp, identity)
rec.summaryLines // [String]
rec.identity // VysloIdentity for the recording as a whole
rec.ownerVerified // shorthand: identity.verdict == .owner
// Needs .recordingsAudio in the grant.
let audio: Data = try await vyslo.fetchRecordingAudio(recordingID)
Speaker identity
let id: VysloIdentity = rec.segments[0].identity
id.verdict // .owner, .likelyOwner or .other
id.source // .device, .hostedReview or .selfHosted
id.evidence // .verified, .unverified or .unavailable
id.serverID // which VYSLO server issued this result
A verdict is only as strong as its source. .device with .verified evidence
is a result the card signed for that recording; .hostedReview is the VYSLO cloud
review; .selfHosted is a claim made by a server someone else runs. Voiceprint
templates, embeddings and raw scores are never exposed.
判決的可信度取決於來源。.device 且證據為 .verified,表示由卡片為該錄音簽名的結果;
.hostedReview 是 VYSLO 雲端複核;.selfHosted 是他人自行部署的服務器所作的聲明。
聲紋模板、embedding 與原始分數永不暴露。
Lists, insights and transcription
// Needs .recordingsRead. Paged, newest first.
let page = try await vyslo.listRecordings(from: weekStart, to: .now, includesMe: true)
page.items // [VysloRecordingSummary]
page.nextCursor
// Needs .recordingsRead.
let hits = try await vyslo.searchTranscripts("budget", from: monthStart)
// Needs .insightsRead.
let week = try await vyslo.fetchWeeklyInsights() // talk time, speaking share, topics, to-dos
// Needs .deviceStatusRead. Cloud snapshot, with observedAt.
let cards = try await vyslo.listDevices()
// Needs .transcriptionCreate. Any audio file, not only card recordings.
let job = try await vyslo.transcribe(fileURL, summarize: true)
Cloud status from listDevices() is the last snapshot the card reported through a
phone, returned with observedAt; for live values connect over
BLE.
listDevices() 返回的是卡片最近一次經手機上報的快照,附 observedAt;要實時值請經
BLE 連接。
BLE direct connection
The same VysloOpen instance talks to the card directly over Bluetooth LE with Core Bluetooth: scan, pair, start and stop recording, read card status, receive card events and pull audio off the card. The VYSLO app does not need to be running. The card checks your capability token itself, so the scopes the user approved are enforced on the card.
同一個 VysloOpen 實例經 Core Bluetooth 直接連接卡片:掃描、配對、起停錄音、讀取卡片狀態、接收卡片事件、從卡上導出音頻。運行時不需要 VYSLO App。卡片自己校驗你的能力令牌,用戶同意的權限在卡片上就被執行。
The first time your app reads protected data, iOS shows its own pairing prompt; if this iPhone is already paired with the card, for example through the VYSLO app, no prompt appears, and users remove a pairing in Settings → Bluetooth. With bluetooth-central enabled, a connected card keeps delivering events while your app is in the background, and the SDK restores the connection through Core Bluetooth state restoration when iOS relaunches your app. Background scans find cards by their service and run less often than in the foreground.
你的 App 第一次讀取受保護數據時,iOS 會彈出系統配對框;若這台 iPhone 已與卡片配對(例如經 VYSLO App),則不會再彈。用戶在「設置 → 藍牙」裡移除配對。開啟 bluetooth-central 後,App 在後台時已連接的卡片仍會持續送達事件;iOS 重新拉起你的 App 時,SDK 會經 Core Bluetooth 狀態恢復重建連接。後台掃描按服務篩選卡片,頻率低於前台。
Scan and connect
let scan = vyslo.ble.startScan { card in
card.name // the name the user gave the card in the VYSLO app
card.identifier // stable per iPhone
card.rssi
}
// … once the user has picked a card
scan.stop()
let session: VysloCardSession = try await vyslo.ble.connect(card)
// Needs .deviceStatusRead.
let s = try await session.status()
s.batteryPercent
s.isCharging
s.storageUsedBytes
s.firmwareVersion
s.isRecording
s.isListening // smart wake is listening, nothing is being stored
s.smartWakeEnabled
session.close() // disconnects and releases the control lease if you hold it
Start and stop recording
// Needs .captureControl.
try await session.acquireControl() // exclusive control lease; throws .leaseHeld if another app has it
try await session.startRecording()
try await session.pauseRecording()
try await session.resumeRecording()
try await session.stopRecording()
try await session.releaseControl()
Several apps can be connected to the same card at once, and all of them can read status and receive events. Only the holder of the control lease can start or stop recording, and that includes the VYSLO app itself. The lease ends when you release it, close the session or lose the link. A recording the user starts with the card's own button is never blocked by a lease.
多個 App 可以同時連接同一張卡,都能讀狀態、收事件;只有持有控制權租約的一方能起停錄音——VYSLO App 自己也不例外。 主動釋放、關閉會話或連接斷開時,租約即結束。用戶用卡片按鍵發起的錄音,永遠不會被租約擋住。
Card events
// Needs .eventsSubscribe.
for await event in session.events {
switch event {
case .recordingStarted: break
case .recordingStopped(let id): _ = id
case .smartWakeTriggered: break
case .listeningChanged(let on): _ = on
case .buttonPressed: break
case .chargingChanged(let charging): _ = charging
}
}
Events carry no audio and no transcript text.
事件裡不含音頻,也不含轉寫正文。
Pull audio off the card
// Needs .recordingsRead to list, .recordingsAudio to export.
let onCard: [VysloCardRecording] = try await session.listRecordings()
// id, startedAt, duration, sizeBytes
try await session.exportAudio(
recordingID: onCard[0].id,
format: .wav, // .opus, .wav or .pcm
to: documents.appendingPathComponent("recording.wav")
) { progress in /* 0.0 – 1.0 */ }
| Format | What you get |
|---|---|
.opus | The file exactly as the card stores it: Opus, 16 kHz, mono. Smallest and fastest |
.wav | 16-bit PCM in a WAV container, 16 kHz, mono. Decoded on the phone |
.pcm | Raw 16-bit little-endian samples, 16 kHz, mono. Decoded on the phone |
| 格式 | 得到什麼 |
|---|---|
.opus | 卡上存儲的原樣檔案:Opus、16 kHz、單聲道。體積最小、最快 |
.wav | WAV 封裝的 16 位 PCM,16 kHz、單聲道。在手機上解碼 |
.pcm | 原始 16 位小端採樣,16 kHz、單聲道。在手機上解碼 |
Hidden and rejected recordings are never listed or exported over BLE, the same rule as the cloud.
隱藏與被拒的錄音在 BLE 上同樣不會被列出、也不能導出,與雲端規則一致。
Fast transfer over Wi-Fi
// Needs .recordingsAudio. For many or long recordings.
try await session.fastTransfer(
recordingIDs: onCard.map(\.id),
format: .opus,
to: documents.appendingPathComponent("vyslo")
) { done, total in }
The card turns on its own Wi-Fi hotspot and the iPhone joins it through NEHotspotConfiguration; iOS asks the user to join each time, and the first transfer also asks for local network access. Bluetooth stays connected for control while the files move, and the card turns the hotspot off when the transfer ends.
卡片打開自己的 Wi-Fi 熱點,iPhone 經 NEHotspotConfiguration 加入;iOS 每次都會請用戶確認加入,第一次傳輸還會請求本地網絡權限。傳輸期間藍牙保持連接用於控制,傳完卡片自動關閉熱點。
Live audio
// Needs .audioLive. Your app must be in the foreground.
let stream = try await session.startLiveAudio(format: .pcm) { frame in
// 16 kHz mono frames while the user holds the card's button
}
stream.onPushToTalk = { pressed in } // button down / up
stream.stop()
Hold the card's button and speak: audio streams into the app the user is looking at, for dictation, voice input or your own speech pipeline. While the stream is open the button works as push-to-talk instead of starting a recording; nothing is stored on the card, and the stream stops when your app leaves the foreground.
按住卡片按鍵說話,音頻實時流入用戶眼前的 App,可用於聽寫、語音輸入或你自己的語音處理鏈路。串流期間按鍵作為按住說話使用、不會發起錄音,卡上不存儲任何內容;你的 App 退到後台時串流自動停止。
What the card checks
- The card verifies your capability token itself (signature, expiry, scopes and the user it was issued for), so BLE works with no network.
- A card bound to a different user refuses your token.
- A token past its 60-day grace period is refused until
renewIfNeeded()fetches a fresh one online. A revocation reaches the card the next time it syncs. - Deleting, unbinding and replacing a voiceprint also need a press on the card itself; see Device management.
- 卡片自己校驗你的能力令牌(簽名、有效期、權限、簽發給哪位用戶),所以 BLE 不需要網絡。
- 綁定在其他用戶名下的卡片,會拒絕你的令牌。
- 超過 60 天寬限期的令牌會被拒絕,直到
renewIfNeeded()在線取回新令牌。撤銷會在卡片下次同步時生效。 - 刪除、解綁與重新註冊聲紋,還需要在卡片上按鍵確認,見設備管理。
BLE errors
BLE calls throw VysloBleError; switch on its reason:
BLE 調用拋出 VysloBleError,按 reason 分支:
| reason | Meaning |
|---|---|
.bluetoothOff | Bluetooth is off. Ask the user to turn it on |
.permissionDenied | A runtime Bluetooth permission has not been granted |
.notAuthorized | No token is stored, or the card refused it: expired, revoked, another user's card, or a scope missing |
.leaseHeld | Another app holds the control lease |
.cardBusy | The card cannot take the request right now, for example during a firmware update |
.disconnected | The link dropped. Call connect(card) again |
.notForeground | Live audio was requested while your app is not in the foreground |
.confirmationTimeout | An operation that needs a press on the card got none within 10 seconds, and was not carried out |
| reason | 含義 |
|---|---|
.bluetoothOff | 藍牙未開啟,請用戶打開 |
.permissionDenied | 運行時藍牙權限未授予 |
.notAuthorized | 沒有本地令牌,或卡片拒絕了它:過期、已撤銷、他人的卡,或缺少權限 |
.leaseHeld | 控制權租約在其他 App 手上 |
.cardBusy | 卡片暫時無法處理請求,例如正在升級固件 |
.disconnected | 連接斷開,重新調用 connect(card) |
.notForeground | 你的 App 不在前台時請求了實時音頻 |
.confirmationTimeout | 需要在卡上按鍵確認的操作,10 秒內沒有收到按鍵,操作未執行 |
Device management
Everything the VYSLO app does to a card goes through these same calls: settings, voiceprint
enrolment, firmware updates and deletion. All of them run over a BLE session
and need .deviceManage.
VYSLO App 對卡片做的所有操作,走的都是這同一組調用:設置、聲紋註冊、固件升級與刪除。全部經
BLE 會話執行,需要 .deviceManage。
Smart wake and card settings
try await session.updateSettings(smartWakeEnabled: true)
try await session.updateSettings(name: "Work card")
let s = try await session.status()
s.smartWakeEnabled
s.isListening
With smart wake on, the card listens for its owner's voice and starts recording only when it hears
them; nothing is stored while it listens. With it off, the card records only when the button is
pressed or an app starts a recording. Smart wake changes arrive as
SmartWakeTriggered and ListeningChanged card events.
開啟智能喚醒後,卡片監聽機主的聲音,聽到機主說話才開始錄音;監聽期間不存儲任何內容。關閉後,卡片只在按鍵或 App
發起時錄音。智能喚醒的變化以 SmartWakeTriggered 與 ListeningChanged
卡片事件送達。
Voiceprint enrolment
// About 12 seconds of the user's own voice. Needs network.
for try await step in session.enrollVoiceprint() {
switch step {
case .recording(let level): showLevel(level) // the user speaks now
case .processing: showProgress()
case .done: showDone()
}
}
The card records the sample, the cloud builds the voiceprint and writes it back to the card, where smart wake and speaker identity use it. Replacing an existing voiceprint asks for a press on the card first.
卡片錄下樣本,雲端生成聲紋並寫回卡片,供智能喚醒與說話人身份使用。若卡上已有聲紋,替換前需要先在卡上按鍵確認。
Firmware updates
if let update = try await session.checkFirmwareUpdate() { // nil when up to date
try await session.installFirmware(update, overWiFi: true) { progress in }
}
The card accepts only images VYSLO signed; the SDK fetches the release meant for this card. Over Wi-Fi the card downloads through its own hotspot, much faster than BLE. The new image takes over on the next restart, and if it does not come up healthy the card rolls back to the previous one by itself.
卡片只接受 VYSLO 簽名的鏡像;SDK 會取回適用於這張卡的版本。走 Wi-Fi 時卡片經自己的熱點下載,比 BLE 快得多。 新鏡像在下次重啟時接管;若啟動後不健康,卡片會自動回滾到上一個版本。
Deleting, unbinding and on-card confirmation
try await session.deleteRecording(recordingID)
// These wait for a press on the card, then run.
try await session.deleteAllRecordings()
try await session.resetVoiceprint()
try await session.unbind()
Deleting all recordings, resetting a voiceprint and unbinding put the card into a pending state:
the LED shows the confirmation signal and the user has 10 seconds to press the button. No press,
no action, and the call throws .confirmationTimeout. The rule is enforced by the
card, so it applies to every app, the VYSLO app included.
刪除全部錄音、重置聲紋與解綁會讓卡片進入待確認狀態:LED 顯示確認提示,用戶需在 10 秒內按鍵。沒有按鍵就不執行,
調用拋出 .confirmationTimeout。這條規則由卡片執行,對所有 App 生效,包括 VYSLO App 自己。
LED and haptics
Choose how the card's LED looks in each state and how it vibrates on each event. Settings are saved
on the card and keep working offline. Every connected app sees the same settings, and a change saved
by one app is pushed to the others. Reading and writing both need .deviceManage.
按卡片狀態設置 LED 的樣子,按卡片事件設置振動方式。設置保存在卡片上,離線照樣生效。所有已連接的 App 看到的是同一份設置,
一個 App 保存後會推送給其他 App。讀取與寫入都需要 .deviceManage。
let caps = try await session.feedback.capabilities() // states, events, modes, ranges, effect catalogue
let current = try await session.feedback.settings() // your overrides, defaults, resolved values, revision
try await session.feedback.updateSettings(
FeedbackPatch()
.setLED("recording.smartWakeOn",
.breathe(color: RGB(0, 60, 100), brightnessPercent: 40,
periodMs: 2400, minBrightnessPercent: 0))
.setLED("idle.smartWakeOff", .off)
.setHaptic("recording.started", .effect("START_CONFIRM"))
.setHaptic("recording.stopped.SILENCE",
.pulses(levelPercent: 60, onMs: 80, offMs: 120, count: 2)),
expectedRevision: current.revision // rejected if another app saved in between
)
// Try before saving. A preview never saves and never fakes a real event.
let preview = try await session.feedback.previewLED(profile, durationMs: 3000)
try await session.feedback.stopPreview(preview.id)
try await session.feedback.previewHaptic(pattern)
try await session.feedback.restoreDefaults(["idle.smartWakeOff"]) // or restoreDefaults() for all
for await change in session.feedback.changes { } // saved elsewhere, defaults restored, state changed
try await session.feedback.status() // the state being shown right now, and why
LED modes
| Mode | Parameters |
|---|---|
off | None. The LED is dark in this state; recording and power are unaffected |
steady | colorRgb, brightnessPercent |
breathe | colorRgb, brightnessPercent, periodMs, minBrightnessPercent |
blink | colorRgb, brightnessPercent, onMs, offMs |
| 模式 | 參數 |
|---|---|
off | 無。該狀態下熄燈,不影響錄音與電源 |
steady | colorRgb、brightnessPercent |
breathe | colorRgb、brightnessPercent、periodMs、minBrightnessPercent |
blink | colorRgb、brightnessPercent、onMs、offMs |
colorRgb takes 0–100 per channel and brightnessPercent 0–100. Times are
multiples of timeQuantumMs from getCapabilities(). A value the card cannot
show exactly is refused with the reason, never silently rounded.
colorRgb 每通道 0–100,brightnessPercent 0–100。時間須為
getCapabilities() 給出的 timeQuantumMs 的整數倍。卡片無法精確呈現的值會被拒絕並說明原因,
不會被悄悄取整。
LED states
| State | When | Default |
|---|---|---|
idle.smartWakeOnidle.smartWakeOff | Standing by | Steady, 2% |
monitoring | Smart wake is listening | Same as standby |
recording.smartWakeOnrecording.smartWakeOff | Recording | Breathe, peak 30%, 2 s |
recording.paused | Recording paused | Same as recording |
enrollment | Recording a voiceprint sample | Same as recording |
charging | Charging | Steady amber |
chargeComplete | Charge complete | The state underneath |
lowBattery | Below 10% and not charging | Amber blink, 500 ms on and off |
pairing | Pairing window open | The state underneath |
authorizationPending | Waiting for an on-card confirmation | Confirmation signal; cannot be turned fully off |
bondClearFeedback | Unbind completed | Red blink, three times |
powerOnpowerOff | Power-on and power-off windows | Off |
| 狀態 | 何時 | 默認 |
|---|---|---|
idle.smartWakeOnidle.smartWakeOff | 待機 | 常亮,2% |
monitoring | 智能喚醒監聽中 | 同待機 |
recording.smartWakeOnrecording.smartWakeOff | 錄音中 | 呼吸,峰值 30%,週期 2 秒 |
recording.paused | 錄音已暫停 | 同錄音 |
enrollment | 正在錄聲紋樣本 | 同錄音 |
charging | 充電中 | 琥珀色常亮 |
chargeComplete | 充滿 | 沿用下層狀態 |
lowBattery | 電量低於 10% 且未充電 | 琥珀色閃爍,亮滅各 500 ms |
pairing | 配對窗口打開 | 沿用下層狀態 |
authorizationPending | 等待卡上確認 | 確認提示;不能完全熄滅 |
bondClearFeedback | 解綁完成 | 紅色閃爍三次 |
powerOnpowerOff | 開機與關機窗口 | 熄滅 |
The card picks the state from what is actually happening; your settings change only how that state
looks. When several hold at once the higher one wins: unbind feedback, power off, confirmation
pending, low battery, recording, power on, charging, charge complete, pairing, listening, standby.
A state can also inherit another one, a specific key or @idle, @recording
or @underlying (the next state down).
卡片按實際發生的事選擇狀態;你的設置只改變該狀態的呈現。多個狀態同時成立時取優先級高者:解綁反饋、關機、待確認、
低電量、錄音、開機、充電、充滿、配對、監聽、待機。狀態也可以繼承另一個狀態:具體的 key,或 @idle、
@recording、@underlying(下一層狀態)。
Haptic events
| Event | When | Default |
|---|---|---|
recording.started | A recording actually starts, from the button, an app or smart wake | START_CONFIRM |
recording.stopped | A recording stops | STOP_CONFIRM |
recording.stopped.<origin> | Stopped for a specific reason: silence, smart wake turned off, a firmware update, and others | Same as recording.stopped |
recording.pausedrecording.resumed | Pause and resume | Off |
monitoring.startedmonitoring.ended | Smart wake starts or stops listening | Off |
recording.discarded.l2Rejectrecording.discarded.noSpeech | Smart wake heard someone else, or no speech, and kept nothing | Off |
recording.interrupted.<reason> | A recording cut short by pairing, shutdown or a critical battery | Off |
pairing.started | Pairing window opens | PAIRING |
operation.rejected | The card refused a request, per reason: low battery, busy, shutting down | REJECT |
power.onpower.offpower.off.criticalBattery | Power on and off by the button, or off on a critical battery | Double buzz |
| 事件 | 何時 | 默認 |
|---|---|---|
recording.started | 錄音真正開始:按鍵、App 或智能喚醒發起 | START_CONFIRM |
recording.stopped | 錄音停止 | STOP_CONFIRM |
recording.stopped.<origin> | 因特定原因停止:靜音、關閉智能喚醒、固件升級等 | 同 recording.stopped |
recording.pausedrecording.resumed | 暫停與繼續 | 關閉 |
monitoring.startedmonitoring.ended | 智能喚醒開始或結束監聽 | 關閉 |
recording.discarded.l2Rejectrecording.discarded.noSpeech | 智能喚醒聽到的是別人或沒有語音,未保留任何內容 | 關閉 |
recording.interrupted.<reason> | 錄音因配對、關機或電量耗盡被中斷 | 關閉 |
pairing.started | 配對窗口打開 | PAIRING |
operation.rejected | 卡片拒絕了請求,可按原因區分:低電量、忙碌、正在關機 | REJECT |
power.onpower.offpower.off.criticalBattery | 按鍵開關機,或電量耗盡關機 | 雙振 |
A haptic pattern is either an effect from the card's catalogue (getCapabilities() lists
the ones this card's motor supports) or pulses: levelPercent 0–100, onMs,
offMs and count. Limits come from getCapabilities(); anything
outside them is refused with the reason, never silently changed.
振動方式可以是卡片效果目錄中的效果(getCapabilities() 列出這張卡的馬達支援哪些),也可以是脈衝:
levelPercent 0–100、onMs、offMs 與 count。上下限由
getCapabilities() 給出;超出範圍的請求會被拒絕並說明原因,不會被悄悄改動。
Battery protection, data protection and on-card confirmation cannot be switched off from feedback
settings. A custom colour can make two states look alike, so show state in your own UI from
status(), not from the LED.
電池保護、數據保護與卡上確認,都不能通過反饋設置關閉。自定義顏色可能讓兩個狀態看起來一樣,所以在你自己的界面裡請以
status() 顯示狀態,而不是看 LED。
Token lifecycle
// Call on foreground start. Internally rate-limited:
// does nothing if the stored token is younger than minAgeDays.
try await vyslo.renewIfNeeded() // default 7 days
try await vyslo.renewIfNeeded(minAgeDays: 3)
vyslo.setToken(token) // adopt a token you obtained yourself
vyslo.clearToken() // forget it (e.g. on your own sign-out)
| Behaviour | Value |
|---|---|
| Token lifetime from issue | 60 days — the offline grace period |
| Renewal cadence | renewIfNeeded() acts once the token is 7 days old |
| User revoked, or app banned | 403 — the SDK clears the stored token, then throws |
| Invalid or expired token | 401 — the SDK clears the stored token, then throws |
| Where the user revokes | VYSLO app → 我的 → 已授權的應用 |
| 行為 | 取值 |
|---|---|
| 令牌自簽發起的有效期 | 60 天——離線寬限期 |
| 續簽節奏 | renewIfNeeded() 在令牌滿 7 天後才動作 |
| 用戶撤銷或應用被封禁 | 403——SDK 先清除本地令牌再拋異常 |
| 令牌無效或過期 | 401——SDK 先清除本地令牌再拋異常 |
| 用戶在哪裡撤銷 | VYSLO App → 我的 → 已授權的應用 |
Errors
do {
let rec = try await vyslo.fetchRecording(recordingID)
} catch let error as VysloOpenError {
switch error.statusCode {
case 401, 403: reauthorize() // token already cleared by the SDK
case 404: dropLocalCopy() // hidden, deleted, or not this user's recording
default: retryLater(error.body)
}
}
VysloOpenError carries statusCode and the raw response body. A 404 is deliberately indistinguishable between "does not exist" and "exists but is hidden or belongs to someone else".
VysloOpenError 帶 statusCode 與原始回應 body。404 故意不區分「不存在」與「存在但被隱藏或屬於他人」。
Webhook signature helper
// Reference implementation — your server does the same thing in its own language.
let ok = VysloOpen.verifyWebhookSignature(
webhookSecret: secret,
rawBody: body, // exactly the bytes you received
signature: request.value(forHTTPHeaderField: "X-Vyslo-Signature") ?? ""
)
Reference
| Member | Meaning |
|---|---|
VysloOpen(appId:baseURL:) | Construct; baseURL defaults to the official cloud |
authorizeURL(scopes:callbackURI:) | URL that opens the VYSLO consent screen |
handleAuthorizationCallback(_:) | Extract and store the token from the callback URL; true on success |
renewIfNeeded(minAgeDays:) | Rate-limited renewal; true if it actually renewed |
fetchRecording(_:) / fetchRecordingAudio(_:) | Transcript, summary, speaker identity / original audio |
listRecordings(…) / searchTranscripts(…) | Paged recording list / search across transcripts |
fetchWeeklyInsights() / listDevices() / transcribe(_:summarize:) | Weekly insights / cards on the account / transcribe any audio file |
ble.startScan(_:) / ble.connect(_:) | Scan for nearby cards / connect and present the token, returning a VysloCardSession |
session.status() / session.events | Live card status / card events as an AsyncStream |
session.acquireControl() … stopRecording() | Control lease and capture: start, pause, resume, stop |
session.listRecordings() / exportAudio(…) / fastTransfer(…) | Recordings on the card / export one over BLE / bulk over Wi-Fi |
session.startLiveAudio(format:onFrame:) | Push-to-talk live audio stream |
session.updateSettings(…) / enrollVoiceprint() / installFirmware(…) | Smart wake and name / voiceprint enrolment / signed firmware |
session.deleteAllRecordings() / resetVoiceprint() / unbind() | Need a press on the card |
session.feedback | LED and haptics: capabilities, settings, preview, restore defaults, changes |
VysloOpen.verifyWebhookSignature(…) | HMAC-SHA256 verification reference |
VysloScope | .recordingsRead … .transcriptionCreate — see Scopes & tokens |