@readymadeit/mcp 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -2
- package/bundle/LICENSE +201 -0
- package/bundle/boards/aggregator-demo-board.json +54 -0
- package/bundle/boards/alpaca-board.json +316 -0
- package/bundle/boards/animate-board.json +159 -0
- package/bundle/boards/ascii-art-demo-board.json +33 -0
- package/bundle/boards/ascii-cam-board.json +184 -0
- package/bundle/boards/asset-demo-board.json +54 -0
- package/bundle/boards/audio-encode-demo-board.json +153 -0
- package/bundle/boards/audiolize-board.json +152 -0
- package/bundle/boards/batcher-demo-board.json +43 -0
- package/bundle/boards/buffer-demo-board.json +53 -0
- package/bundle/boards/cache-demo-board.json +59 -0
- package/bundle/boards/camera-demo-board.json +28 -0
- package/bundle/boards/canvas-demo-board.json +55 -0
- package/bundle/boards/changes-demo-board.json +60 -0
- package/bundle/boards/court-booking-browser-demo-board.json +221 -0
- package/bundle/boards/court-booking-demo-board.json +218 -0
- package/bundle/boards/current-time-board.json +32 -0
- package/bundle/boards/debounce-demo-board.json +43 -0
- package/bundle/boards/decrypt-demo-board.json +47 -0
- package/bundle/boards/delay-demo-board.json +28 -0
- package/bundle/boards/detect-demo-board.json +64 -0
- package/bundle/boards/document-extract-demo-board.json +55 -0
- package/bundle/boards/dropitapp-board.json +138 -0
- package/bundle/boards/encode-demo-board.json +39 -0
- package/bundle/boards/encrypt-board.json +185 -0
- package/bundle/boards/encrypt-demo-board.json +39 -0
- package/bundle/boards/face-alert-board.json +181 -0
- package/bundle/boards/fetcher-demo-board.json +40 -0
- package/bundle/boards/fft-demo-board.json +28 -0
- package/bundle/boards/filter-demo-board.json +55 -0
- package/bundle/boards/flat-map-demo-board.json +42 -0
- package/bundle/boards/frame-limit-test-board.json +130 -0
- package/bundle/boards/game-board.json +413 -0
- package/bundle/boards/game-of-life-board.json +139 -0
- package/bundle/boards/game-of-life-demo-board.json +34 -0
- package/bundle/boards/gather-demo-board.json +62 -0
- package/bundle/boards/group-by-demo-board.json +62 -0
- package/bundle/boards/hacker-demo-board.json +45 -0
- package/bundle/boards/hacker-news-board.json +741 -0
- package/bundle/boards/hash-demo-board.json +40 -0
- package/bundle/boards/health-log-browser-demo-board.json +537 -0
- package/bundle/boards/health-log-demo-board.json +498 -0
- package/bundle/boards/helloworld-board.json +57 -0
- package/bundle/boards/hold-demo-board.json +67 -0
- package/bundle/boards/hold-scopes-demo-board.json +141 -0
- package/bundle/boards/http-client-browser-demo-board.json +379 -0
- package/bundle/boards/http-client-demo-board.json +380 -0
- package/bundle/boards/image-picker-demo-board.json +22 -0
- package/bundle/boards/imap-email-demo-board.json +35 -0
- package/bundle/boards/injector-demo-board.json +55 -0
- package/bundle/boards/iterator-demo-board.json +55 -0
- package/bundle/boards/limit-demo-board.json +66 -0
- package/bundle/boards/link-debugger.json +32 -0
- package/bundle/boards/live-location-demo-board.json +93 -0
- package/bundle/boards/live-radio-cloud-demo-board.json +216 -0
- package/bundle/boards/live-radio-cloud-direct-demo-board.json +311 -0
- package/bundle/boards/live-radio-demo-board.json +211 -0
- package/bundle/boards/live-radio-members-demo-board.json +314 -0
- package/bundle/boards/local-storage-demo-board.json +30 -0
- package/bundle/boards/lz-compress-demo-board.json +52 -0
- package/bundle/boards/map-demo-board.json +46 -0
- package/bundle/boards/match-filter-demo-board.json +43 -0
- package/bundle/boards/meeting-poll-browser-demo-board.json +649 -0
- package/bundle/boards/meeting-poll-demo-board.json +650 -0
- package/bundle/boards/microphone-monitor-demo-board.json +28 -0
- package/bundle/boards/microphone-speaker-demo-board.json +86 -0
- package/bundle/boards/monitor-demo-board.json +52 -0
- package/bundle/boards/mounted-endpoint-demo-board-hkp-rt.json +84 -0
- package/bundle/boards/mounted-endpoint-demo-board.json +84 -0
- package/bundle/boards/nested-rhythm-demo-board.json +1160 -0
- package/bundle/boards/next-bus-board.json +52 -0
- package/bundle/boards/next-bus-switch-board.json +79 -0
- package/bundle/boards/night-groove-demo-board.json +742 -0
- package/bundle/boards/noise-alert-board.json +146 -0
- package/bundle/boards/offer-followup-demo-board.json +291 -0
- package/bundle/boards/offer-intake-demo-board.json +289 -0
- package/bundle/boards/offer-intake-hetzner-demo-board.json +286 -0
- package/bundle/boards/offer-intake-imap-demo-board.json +289 -0
- package/bundle/boards/offer-intake-imap-hetzner-demo-board.json +354 -0
- package/bundle/boards/p2p-receiver-demo-board.json +65 -0
- package/bundle/boards/p2p-sender-demo-board.json +94 -0
- package/bundle/boards/pad-groove-demo-board.json +644 -0
- package/bundle/boards/peer-chat-board.json +109 -0
- package/bundle/boards/peer-chat-node-board.json +97 -0
- package/bundle/boards/qr-code-demo-board.json +22 -0
- package/bundle/boards/queue-demo-board.json +239 -0
- package/bundle/boards/reduce-demo-board.json +54 -0
- package/bundle/boards/remote-named-demo-board-hkp-rt.json +61 -0
- package/bundle/boards/remote-named-demo-board.json +61 -0
- package/bundle/boards/rss-demo-board.json +530 -0
- package/bundle/boards/rss-radio-board.json +515 -0
- package/bundle/boards/rss-radio-brief-one-board.json +237 -0
- package/bundle/boards/rss-radio-editor-unit-board.json +555 -0
- package/bundle/boards/rss-radio-library-unit-board.json +293 -0
- package/bundle/boards/rss-radio-unit-board.json +809 -0
- package/bundle/boards/rss-radio-voice-unit-board.json +146 -0
- package/bundle/boards/runtime-configs/hold-example.json +41 -0
- package/bundle/boards/runtime-configs/injector-example.json +23 -0
- package/bundle/boards/runtime-configs/speech-to-text-example.json +35 -0
- package/bundle/boards/runtime-configs/text-generation-example.json +31 -0
- package/bundle/boards/runtime-configs/text-to-speech-example.json +28 -0
- package/bundle/boards/runtime-configs/text-to-speech-inflect-example.json +28 -0
- package/bundle/boards/runtime-configs/ws-audio-playback-example.json +23 -0
- package/bundle/boards/select-demo-board.json +32 -0
- package/bundle/boards/send-ntfy-board.json +52 -0
- package/bundle/boards/shared-costs-demo-board.json +361 -0
- package/bundle/boards/sign-demo-board.json +41 -0
- package/bundle/boards/skill-router-demo-board.json +210 -0
- package/bundle/boards/smooth-demo-board.json +91 -0
- package/bundle/boards/smtp-email-demo-board.json +34 -0
- package/bundle/boards/sort-demo-board.json +55 -0
- package/bundle/boards/sound-demo-board.json +65 -0
- package/bundle/boards/spectral-modifier-board.json +98 -0
- package/bundle/boards/speech-synth-demo-board.json +37 -0
- package/bundle/boards/speech-to-text-demo-board.json +146 -0
- package/bundle/boards/spotify-board.json +75 -0
- package/bundle/boards/spread-demo-board.json +35 -0
- package/bundle/boards/sql-demo-board.json +56 -0
- package/bundle/boards/sql-explorer-board.json +447 -0
- package/bundle/boards/sql-explorer-node-board.json +474 -0
- package/bundle/boards/sql-import-board.json +156 -0
- package/bundle/boards/state-demo-board.json +65 -0
- package/bundle/boards/stopper-demo-board.json +40 -0
- package/bundle/boards/storage-demo-board.json +193 -0
- package/bundle/boards/store-demo-board.json +63 -0
- package/bundle/boards/swimlanes-demo-board.json +197 -0
- package/bundle/boards/switch-demo-board.json +51 -0
- package/bundle/boards/syn-board.json +19 -0
- package/bundle/boards/syn-booking-unit-board.json +998 -0
- package/bundle/boards/syn-conversations-anthropic-demo-board.json +925 -0
- package/bundle/boards/syn-conversations-demo-board.json +925 -0
- package/bundle/boards/syn-hotels-unit-board.json +287 -0
- package/bundle/boards/syn-mail-desk-board.json +25 -0
- package/bundle/boards/syn-mail-desk-unit-board.json +254 -0
- package/bundle/boards/telegram-listener-demo-board.json +31 -0
- package/bundle/boards/text-generation-anthropic-demo-board.json +66 -0
- package/bundle/boards/text-generation-demo-board.json +51 -0
- package/bundle/boards/text-generation-local-demo-board.json +80 -0
- package/bundle/boards/text-to-speech-demo-board.json +58 -0
- package/bundle/boards/timeline-demo-board.json +477 -0
- package/bundle/boards/timer-demo-board.json +35 -0
- package/bundle/boards/tracks-demo-board.json +95 -0
- package/bundle/boards/trigger-pad-demo-board.json +47 -0
- package/bundle/boards/triggerpad-board.json +31 -0
- package/bundle/boards/triggerpad-recorder-board.json +56 -0
- package/bundle/boards/uuid-generator-demo-board.json +166 -0
- package/bundle/boards/voice-assistant-demo-board.json +230 -0
- package/bundle/boards/voice-assistant-hkp-rt-demo-board.json +239 -0
- package/bundle/boards/voice-assistant-local-demo-board.json +232 -0
- package/bundle/boards/voice-assistant-skills-demo-board.json +316 -0
- package/bundle/boards/voice-assistant-skills-local-demo-board.json +316 -0
- package/bundle/boards/xy-pad-demo-board.json +36 -0
- package/bundle/docs/content/board-json.md +277 -0
- package/bundle/docs/content/boards/alpaca-board.md +56 -0
- package/bundle/docs/content/boards/animate-board.md +40 -0
- package/bundle/docs/content/boards/ascii-cam-board.md +37 -0
- package/bundle/docs/content/boards/audiolize-board.md +47 -0
- package/bundle/docs/content/boards/court-booking-browser-demo-board.md +43 -0
- package/bundle/docs/content/boards/court-booking-demo-board.md +120 -0
- package/bundle/docs/content/boards/current-time-board.md +32 -0
- package/bundle/docs/content/boards/dropitapp-board.md +57 -0
- package/bundle/docs/content/boards/encrypt-board.md +36 -0
- package/bundle/docs/content/boards/face-alert-board.md +50 -0
- package/bundle/docs/content/boards/game-board.md +69 -0
- package/bundle/docs/content/boards/game-of-life-board.md +47 -0
- package/bundle/docs/content/boards/hacker-news-board.md +76 -0
- package/bundle/docs/content/boards/health-log-browser-demo-board.md +56 -0
- package/bundle/docs/content/boards/health-log-demo-board.md +138 -0
- package/bundle/docs/content/boards/helloworld-board.md +40 -0
- package/bundle/docs/content/boards/link-debugger.md +31 -0
- package/bundle/docs/content/boards/live-radio-cloud-demo-board.md +70 -0
- package/bundle/docs/content/boards/live-radio-cloud-direct-demo-board.md +89 -0
- package/bundle/docs/content/boards/live-radio-demo-board.md +102 -0
- package/bundle/docs/content/boards/live-radio-members-demo-board.md +91 -0
- package/bundle/docs/content/boards/meeting-poll-browser-demo-board.md +45 -0
- package/bundle/docs/content/boards/meeting-poll-demo-board.md +121 -0
- package/bundle/docs/content/boards/mounted-endpoint-demo-board-hkp-rt.md +50 -0
- package/bundle/docs/content/boards/nested-rhythm-demo-board.md +165 -0
- package/bundle/docs/content/boards/next-bus-board.md +38 -0
- package/bundle/docs/content/boards/next-bus-switch-board.md +44 -0
- package/bundle/docs/content/boards/night-groove-demo-board.md +110 -0
- package/bundle/docs/content/boards/noise-alert-board.md +44 -0
- package/bundle/docs/content/boards/pad-groove-demo-board.md +93 -0
- package/bundle/docs/content/boards/peer-chat-board.md +49 -0
- package/bundle/docs/content/boards/peer-chat-node-board.md +47 -0
- package/bundle/docs/content/boards/rss-radio-board.md +221 -0
- package/bundle/docs/content/boards/send-ntfy-board.md +33 -0
- package/bundle/docs/content/boards/shared-costs-demo-board.md +65 -0
- package/bundle/docs/content/boards/spectral-modifier-board.md +55 -0
- package/bundle/docs/content/boards/spotify-board.md +46 -0
- package/bundle/docs/content/boards/sql-explorer-board.md +70 -0
- package/bundle/docs/content/boards/sql-explorer-node-board.md +76 -0
- package/bundle/docs/content/boards/sql-import-board.md +37 -0
- package/bundle/docs/content/boards/swimlanes-demo-board.md +148 -0
- package/bundle/docs/content/boards/syn-mail-desk-board.md +52 -0
- package/bundle/docs/content/boards/timeline-demo-board.md +115 -0
- package/bundle/docs/content/boards/triggerpad-board.md +32 -0
- package/bundle/docs/content/boards/triggerpad-recorder-board.md +52 -0
- package/bundle/docs/content/boards/voice-assistant-demo-board.md +51 -0
- package/bundle/docs/content/concepts/assets.md +245 -0
- package/bundle/docs/content/concepts/blocks.md +300 -0
- package/bundle/docs/content/concepts/board-assistant.md +307 -0
- package/bundle/docs/content/concepts/board.md +265 -0
- package/bundle/docs/content/concepts/cloud-boards.md +803 -0
- package/bundle/docs/content/concepts/coordinator.md +297 -0
- package/bundle/docs/content/concepts/logging.md +293 -0
- package/bundle/docs/content/concepts/mounts.md +314 -0
- package/bundle/docs/content/concepts/presets.md +360 -0
- package/bundle/docs/content/concepts/remotes.md +423 -0
- package/bundle/docs/content/concepts/runtime.md +664 -0
- package/bundle/docs/content/concepts/scopes.md +256 -0
- package/bundle/docs/content/concepts/service.md +227 -0
- package/bundle/docs/content/concepts/units.md +321 -0
- package/bundle/docs/content/introduction.md +163 -0
- package/bundle/docs/content/llm/circle-text-board.json +55 -0
- package/bundle/docs/content/llm/howto-workflow.md +369 -0
- package/bundle/docs/content/repository.md +161 -0
- package/bundle/docs/content/services/aggregator.md +76 -0
- package/bundle/docs/content/services/analyzer.md +44 -0
- package/bundle/docs/content/services/ascii-art.md +58 -0
- package/bundle/docs/content/services/asset.md +88 -0
- package/bundle/docs/content/services/audio-editor.md +57 -0
- package/bundle/docs/content/services/audio-encode.md +115 -0
- package/bundle/docs/content/services/audio-io.md +173 -0
- package/bundle/docs/content/services/batcher.md +49 -0
- package/bundle/docs/content/services/board-service.md +48 -0
- package/bundle/docs/content/services/browser-sub-service.md +75 -0
- package/bundle/docs/content/services/buffer.md +70 -0
- package/bundle/docs/content/services/cache.md +92 -0
- package/bundle/docs/content/services/camera.md +85 -0
- package/bundle/docs/content/services/canvas.md +196 -0
- package/bundle/docs/content/services/changes.md +87 -0
- package/bundle/docs/content/services/chunked-file-provider.md +63 -0
- package/bundle/docs/content/services/communication-dispatcher.md +190 -0
- package/bundle/docs/content/services/configurator.md +66 -0
- package/bundle/docs/content/services/conversations.md +244 -0
- package/bundle/docs/content/services/debounce.md +45 -0
- package/bundle/docs/content/services/decrypt.md +53 -0
- package/bundle/docs/content/services/delay.md +55 -0
- package/bundle/docs/content/services/detect.md +101 -0
- package/bundle/docs/content/services/document-extract.md +174 -0
- package/bundle/docs/content/services/download.md +67 -0
- package/bundle/docs/content/services/encode.md +63 -0
- package/bundle/docs/content/services/encrypt.md +53 -0
- package/bundle/docs/content/services/fetcher.md +122 -0
- package/bundle/docs/content/services/fft.md +96 -0
- package/bundle/docs/content/services/file-source.md +67 -0
- package/bundle/docs/content/services/filesystem.md +157 -0
- package/bundle/docs/content/services/filter.md +133 -0
- package/bundle/docs/content/services/flat-map.md +62 -0
- package/bundle/docs/content/services/game-of-life.md +93 -0
- package/bundle/docs/content/services/gather.md +135 -0
- package/bundle/docs/content/services/gif-encoder.md +56 -0
- package/bundle/docs/content/services/github.md +85 -0
- package/bundle/docs/content/services/group-by.md +54 -0
- package/bundle/docs/content/services/hacker.md +79 -0
- package/bundle/docs/content/services/hash.md +59 -0
- package/bundle/docs/content/services/hold.md +182 -0
- package/bundle/docs/content/services/http-relay-client.md +48 -0
- package/bundle/docs/content/services/http-uploader.md +62 -0
- package/bundle/docs/content/services/http.md +424 -0
- package/bundle/docs/content/services/if.md +79 -0
- package/bundle/docs/content/services/ifft.md +46 -0
- package/bundle/docs/content/services/image-picker.md +50 -0
- package/bundle/docs/content/services/imap-email.md +104 -0
- package/bundle/docs/content/services/injector.md +129 -0
- package/bundle/docs/content/services/input.md +90 -0
- package/bundle/docs/content/services/iterator.md +186 -0
- package/bundle/docs/content/services/join.md +153 -0
- package/bundle/docs/content/services/limit.md +47 -0
- package/bundle/docs/content/services/local-storage.md +47 -0
- package/bundle/docs/content/services/looper.md +62 -0
- package/bundle/docs/content/services/lz-compress.md +52 -0
- package/bundle/docs/content/services/map.md +257 -0
- package/bundle/docs/content/services/match-filter.md +38 -0
- package/bundle/docs/content/services/microphone-monitor.md +66 -0
- package/bundle/docs/content/services/monitor.md +67 -0
- package/bundle/docs/content/services/ollama.md +116 -0
- package/bundle/docs/content/services/openai-prompt.md +74 -0
- package/bundle/docs/content/services/output.md +107 -0
- package/bundle/docs/content/services/peer-socket.md +61 -0
- package/bundle/docs/content/services/qr-code.md +48 -0
- package/bundle/docs/content/services/queue.md +201 -0
- package/bundle/docs/content/services/reduce.md +51 -0
- package/bundle/docs/content/services/rss.md +232 -0
- package/bundle/docs/content/services/select.md +47 -0
- package/bundle/docs/content/services/sign.md +53 -0
- package/bundle/docs/content/services/skill-router.md +154 -0
- package/bundle/docs/content/services/smooth.md +59 -0
- package/bundle/docs/content/services/smtp-email.md +129 -0
- package/bundle/docs/content/services/sort.md +48 -0
- package/bundle/docs/content/services/sound.md +87 -0
- package/bundle/docs/content/services/speech-synth.md +46 -0
- package/bundle/docs/content/services/speech-to-text.md +172 -0
- package/bundle/docs/content/services/spotify.md +77 -0
- package/bundle/docs/content/services/spread.md +111 -0
- package/bundle/docs/content/services/sql.md +491 -0
- package/bundle/docs/content/services/state.md +57 -0
- package/bundle/docs/content/services/static.md +47 -0
- package/bundle/docs/content/services/stopper.md +55 -0
- package/bundle/docs/content/services/storage.md +155 -0
- package/bundle/docs/content/services/store.md +255 -0
- package/bundle/docs/content/services/sub-service.md +152 -0
- package/bundle/docs/content/services/switch.md +67 -0
- package/bundle/docs/content/services/telegram-listener.md +76 -0
- package/bundle/docs/content/services/text-generation.md +390 -0
- package/bundle/docs/content/services/text-to-speech.md +130 -0
- package/bundle/docs/content/services/timeline.md +333 -0
- package/bundle/docs/content/services/timer.md +113 -0
- package/bundle/docs/content/services/tracks.md +207 -0
- package/bundle/docs/content/services/trigger-pad.md +68 -0
- package/bundle/docs/content/services/uuid-generator.md +48 -0
- package/bundle/docs/content/services/wav-reader.md +66 -0
- package/bundle/docs/content/services/websocket.md +189 -0
- package/bundle/docs/content/services/workflow-board-builder.md +65 -0
- package/bundle/docs/content/services/xy-pad.md +60 -0
- package/bundle/docs/content/targets.md +434 -0
- package/bundle/docs/content/testing.md +263 -0
- package/bundle/docs/content/vocabulary.md +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRegistry.tsx +141 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntime.tsx +202 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApi.ts +306 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApp.ts +310 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeScope.ts +500 -0
- package/bundle/hkp-frontend/src/runtime/browser/UIRegistry.ts +150 -0
- package/bundle/hkp-frontend/src/runtime/browser/assetResolver.ts +123 -0
- package/bundle/hkp-frontend/src/runtime/browser/pipelineNames.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/registry/Default.ts +237 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Aggregator.ts +127 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AggregatorUI.tsx +83 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Analyzer.tsx +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Bars.tsx +33 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/HorizontalSpectrogram.tsx +53 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/VerticalSpectogram.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Waveform.tsx +109 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/index.tsx +99 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArt.ts +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArtUI.tsx +131 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Asset.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AssetUI.tsx +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioDeviceSettings.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditor.ts +129 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditorUI.tsx +549 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioInput.ts +298 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioInputUI.tsx +257 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutput.ts +189 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutputUI.tsx +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Batcher.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BoardService.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BoardServiceUI.tsx +410 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserGather.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserIterator.ts +167 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubService.tsx +583 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubServiceUI.tsx +187 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserTracks.tsx +477 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Buffer.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Cache.ts +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CacheUI.tsx +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Camera.ts +90 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CameraUI.tsx +321 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Canvas.ts +139 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasDraw.ts +543 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasUtils.ts +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/index.tsx +287 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasDraw.test.ts +355 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasUtils.test.ts +89 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/fullscreen.test.tsx +26 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Changes.ts +201 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ChangesUI.tsx +85 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ChunkedFileProvider.tsx +183 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Configurator.ts +258 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ConfiguratorUI.tsx +83 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ConsideredHacker.tsx +28 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Debounce.ts +75 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DebounceUI.tsx +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Decrypt.ts +127 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DecryptUI.tsx +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Delay.tsx +110 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Detect.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DetectUI.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Download.ts +120 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DownloadUI.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Encode.ts +138 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/EncodeUI.tsx +52 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Encrypt.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/EncryptUI.tsx +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FFT.tsx +238 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FFTUI.tsx +112 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackService.ts +247 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackServiceUI.tsx +35 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Fetcher.tsx +232 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FetcherUI.tsx +154 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FileSource.tsx +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Filter.tsx +18 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FilterUI.tsx +89 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FlatMap.ts +68 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FlatMapUI.tsx +38 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLife.ts +210 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRenderer.ts +182 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRendererUI.tsx +48 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeUI.tsx +58 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GatherUI.tsx +84 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GifEncoder.tsx +651 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubAPI.ts +294 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubComponents.tsx +389 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSink.tsx +109 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSinkUI.tsx +143 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSource.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSourceUI.tsx +162 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GroupBy.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HTTPUploader.ts +102 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hacker.ts +73 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HackerUI.tsx +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hash.ts +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HashUI.tsx +45 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hold.ts +288 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HoldUI.tsx +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpClient.ts +558 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpClientUI.tsx +212 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpUploaderTokenInjector.ts +149 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IfService.ts +231 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IfServiceUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ImagePicker.tsx +118 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Injector.ts +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/InjectorUI.tsx +389 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Input.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/InputUI.tsx +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IteratorUI.tsx +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LZCompress.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Limit.ts +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LimitUI.tsx +41 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorage.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorageUI.tsx +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Map.ts +14 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MapUI.tsx +334 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MapUIModeAssist.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MatchFilter.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitor.ts +190 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitorUI.tsx +94 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Monitor.tsx +15 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MonitorUI.tsx +281 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPrompt.ts +237 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/BusyIndicator.tsx +18 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/PromptInput.tsx +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/StreamingPreview.tsx +24 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/index.tsx +156 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPrompt.ts +254 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPromptUI.tsx +189 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Output.tsx +145 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OutputUI.tsx +118 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerChatTemplate.json +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerConnection.ts +260 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocket.tsx +268 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketReceiverTemplate.json +29 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketUI.tsx +341 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PlaceholderUI.tsx +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/QrCode.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/QrCodeUI.tsx +197 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Select.ts +40 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SelectUI.tsx +41 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ServiceBase.ts +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariable.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariableUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sign.ts +182 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SignUI.tsx +55 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Smooth.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SmoothUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sort.ts +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SortUI.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sound.ts +541 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SoundUI.tsx +183 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynth.ts +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynthUI.tsx +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Spotify.tsx +111 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyAPI.ts +113 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyComponents.tsx +104 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyUI.tsx +186 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Spread.ts +117 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpreadUI.tsx +38 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sql.ts +448 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SqlInspector.tsx +332 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SqlUI.tsx +226 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/State.ts +149 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Stopper.ts +29 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Switch.tsx +419 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SwitchUI.tsx +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDelta.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDeltaUI.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Timeline.ts +709 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelinePanel.tsx +79 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelineRows.tsx +574 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/Transport.tsx +141 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/index.tsx +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/model.ts +425 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/parts.tsx +708 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Timer.ts +15 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimerUI.tsx +213 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TracksUI.tsx +81 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPad.ts +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPadUI.tsx +160 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/UuidGenerator.ts +74 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/UuidGeneratorUI.tsx +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WaveformDisplay.tsx +104 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClient.ts +114 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClientUI.tsx +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilder.ts +499 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilderUI.tsx +521 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/XYPad.ts +68 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/XYPadUI.tsx +274 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/audio/audioHelper.ts +36 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Batcher.ts +76 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Buffer.ts +142 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Filter.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/GroupBy.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Map.ts +543 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/MatchFilter.ts +49 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Monitor.ts +75 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Reduce.ts +74 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Timer.ts +342 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/bundle.js +7375 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/eval.ts +208 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/expressionEval.ts +223 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/helpers.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/index.ts +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/camera/videoCapture.ts +78 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/changes-modes.ts +9 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/colorpalette.ts +26 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/DetectionPreview.tsx +76 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/mediapipe.ts +34 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/README.md +14 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/blaze_face_short_range.tflite +0 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect-modes.ts +7 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/encode-modes.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/helpers.ts +300 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/http-client-methods.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-database.ts +594 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-dump.ts +192 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspect.ts +130 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspector.css +155 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-modes.ts +27 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/TESTING_APPROACH.md +169 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/__snapshots__/service-ui.future-enhancements.test.tsx.snap +5 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/audio-input-permissions.test.ts +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/audiolizeLanes.test.ts +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/browser-scopes.test.ts +305 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/cache-service.test.ts +53 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/camera-video-capture.test.ts +24 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/changes-service.test.ts +133 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/crypto-secrets.test.ts +196 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/detect-service.test.ts +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/download.test.ts +100 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/expressionEval.test.ts +117 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-panel.test.tsx +35 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-service.test.ts +80 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/filter-service.test.ts +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/github-secrets.test.ts +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/group-by-board.test.ts +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/hold-service.test.ts +199 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-client-service.test.ts +474 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-uploader-token-injector.test.ts +152 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/iterator-gather-service.test.ts +277 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-service.test.ts +1047 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-ui.test.tsx +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/nested-rhythm-board.test.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/night-groove-board.test.ts +290 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/pad-groove-board.test.ts +230 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-host-discovery.test.ts +77 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-socket-discovery.test.ts +69 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/pipeline-names.test.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/secret-canary.test.ts +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-modules.contract.test.ts +229 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.advanced.test.tsx +670 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.future-enhancements.test.tsx +219 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.integration.test.tsx +432 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.smoke.test.tsx +140 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sound-service.test.ts +293 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/spread-service.test.ts +137 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-dump.test.ts +172 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspect.test.ts +163 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspector.test.tsx +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql.test.ts +702 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sub-service-in-block.test.tsx +84 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/switch-service.test.ts +254 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-board.test.ts +218 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-service.test.ts +1002 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui-model.test.ts +230 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui.test.tsx +371 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timer-service.test.ts +902 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-service.test.ts +275 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-ui.test.tsx +126 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/triggerpad-recorder-board.test.ts +100 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/uuid-generator-service.test.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/timeline-core.ts +410 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/DefaultWorkflowBoard.ts +90 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/RefinementSession.ts +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/SystemPromptCatalog.ts +245 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/browser-service-specs.json +110 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/runtime-capabilities.json +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/asset-service.test.ts +73 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/board-persistence.integration.test.tsx +391 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/browser-runtime.identity-binding.test.tsx +81 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/demo-boards.regression.test.tsx +386 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/failed-call-lifecycle.test.ts +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/http-client.integration.test.ts +146 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/nested-run.integration.test.ts +415 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/replay-next.test.ts +391 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/rerun.test.ts +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/run-no-input.test.ts +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-add.integration.test.tsx +256 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-operations.integration.test.tsx +310 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-processing.integration.test.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-removal.integration.test.tsx +454 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-update.identity.integration.test.tsx +145 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/service-operations.integration.test.tsx +511 -0
- package/bundle/hkp-node/src/address.ts +72 -0
- package/bundle/hkp-node/src/assets.ts +582 -0
- package/bundle/hkp-node/src/auth.ts +369 -0
- package/bundle/hkp-node/src/callReport.ts +281 -0
- package/bundle/hkp-node/src/coordinator/auth.ts +85 -0
- package/bundle/hkp-node/src/coordinator/binaryFrame.ts +189 -0
- package/bundle/hkp-node/src/coordinator/boardStore.ts +0 -0
- package/bundle/hkp-node/src/coordinator/bridge.ts +242 -0
- package/bundle/hkp-node/src/coordinator/bridgeProtocol.ts +200 -0
- package/bundle/hkp-node/src/coordinator/coordinator.ts +594 -0
- package/bundle/hkp-node/src/coordinator/facadeAccess.ts +426 -0
- package/bundle/hkp-node/src/coordinator/fileBoardStore.ts +159 -0
- package/bundle/hkp-node/src/coordinator/frameLimits.ts +41 -0
- package/bundle/hkp-node/src/coordinator/index.ts +12 -0
- package/bundle/hkp-node/src/coordinator/join.ts +62 -0
- package/bundle/hkp-node/src/coordinator/logStore.ts +335 -0
- package/bundle/hkp-node/src/coordinator/members.ts +133 -0
- package/bundle/hkp-node/src/coordinator/mount.ts +124 -0
- package/bundle/hkp-node/src/coordinator/participantProtocol.ts +169 -0
- package/bundle/hkp-node/src/coordinator/participants.ts +666 -0
- package/bundle/hkp-node/src/coordinator/router.ts +416 -0
- package/bundle/hkp-node/src/coordinator/session.ts +1610 -0
- package/bundle/hkp-node/src/coordinator/types.ts +95 -0
- package/bundle/hkp-node/src/coordinatorLinks.ts +712 -0
- package/bundle/hkp-node/src/credentialedFetch.ts +122 -0
- package/bundle/hkp-node/src/egress.ts +344 -0
- package/bundle/hkp-node/src/expressionEval.ts +223 -0
- package/bundle/hkp-node/src/index.ts +470 -0
- package/bundle/hkp-node/src/instance.ts +172 -0
- package/bundle/hkp-node/src/mounts.ts +296 -0
- package/bundle/hkp-node/src/origins.ts +201 -0
- package/bundle/hkp-node/src/reset.ts +187 -0
- package/bundle/hkp-node/src/runtime.ts +1345 -0
- package/bundle/hkp-node/src/secrets.ts +292 -0
- package/bundle/hkp-node/src/server.ts +2120 -0
- package/bundle/hkp-node/src/services/asset.ts +150 -0
- package/bundle/hkp-node/src/services/claudeModels.ts +370 -0
- package/bundle/hkp-node/src/services/communication-dispatcher.ts +735 -0
- package/bundle/hkp-node/src/services/conversations.ts +914 -0
- package/bundle/hkp-node/src/services/database.ts +386 -0
- package/bundle/hkp-node/src/services/document-extract.ts +575 -0
- package/bundle/hkp-node/src/services/expression.ts +417 -0
- package/bundle/hkp-node/src/services/fileStore.ts +369 -0
- package/bundle/hkp-node/src/services/filesystem.ts +280 -0
- package/bundle/hkp-node/src/services/gather.ts +335 -0
- package/bundle/hkp-node/src/services/hold.ts +275 -0
- package/bundle/hkp-node/src/services/http-client.ts +525 -0
- package/bundle/hkp-node/src/services/http-server.ts +1222 -0
- package/bundle/hkp-node/src/services/imap-email.ts +627 -0
- package/bundle/hkp-node/src/services/injector.ts +135 -0
- package/bundle/hkp-node/src/services/iterator.ts +203 -0
- package/bundle/hkp-node/src/services/join.ts +226 -0
- package/bundle/hkp-node/src/services/map.ts +632 -0
- package/bundle/hkp-node/src/services/monitor.ts +128 -0
- package/bundle/hkp-node/src/services/nested-pipeline.ts +405 -0
- package/bundle/hkp-node/src/services/peer-server.ts +268 -0
- package/bundle/hkp-node/src/services/queue-store.ts +301 -0
- package/bundle/hkp-node/src/services/queue.ts +316 -0
- package/bundle/hkp-node/src/services/recordStore.ts +341 -0
- package/bundle/hkp-node/src/services/rss-parse.ts +281 -0
- package/bundle/hkp-node/src/services/rss.ts +392 -0
- package/bundle/hkp-node/src/services/smtp-email.ts +398 -0
- package/bundle/hkp-node/src/services/spread.ts +153 -0
- package/bundle/hkp-node/src/services/sql-dump.ts +192 -0
- package/bundle/hkp-node/src/services/sql.ts +429 -0
- package/bundle/hkp-node/src/services/stopper.ts +69 -0
- package/bundle/hkp-node/src/services/storage.ts +332 -0
- package/bundle/hkp-node/src/services/store.ts +609 -0
- package/bundle/hkp-node/src/services/stream.ts +367 -0
- package/bundle/hkp-node/src/services/sub-service.ts +600 -0
- package/bundle/hkp-node/src/services/telegram-listener.ts +263 -0
- package/bundle/hkp-node/src/services/telegram-sender.ts +136 -0
- package/bundle/hkp-node/src/services/text-generation.ts +1249 -0
- package/bundle/hkp-node/src/services/timer.ts +273 -0
- package/bundle/hkp-node/src/services/tracks.ts +381 -0
- package/bundle/hkp-node/src/services/websocket-reader.ts +364 -0
- package/bundle/hkp-node/src/types.ts +465 -0
- package/bundle/hkp-node/src/wsTickets.ts +77 -0
- package/bundle/hkp-node/src/yas.ts +95 -0
- package/bundle/hkp-python/src/hkp/__init__.py +1 -0
- package/bundle/hkp-python/src/hkp/__main__.py +241 -0
- package/bundle/hkp-python/src/hkp/address.py +56 -0
- package/bundle/hkp-python/src/hkp/assets.py +439 -0
- package/bundle/hkp-python/src/hkp/auth.py +252 -0
- package/bundle/hkp-python/src/hkp/binary_frame.py +100 -0
- package/bundle/hkp-python/src/hkp/call_report.py +201 -0
- package/bundle/hkp-python/src/hkp/coordinator_links.py +667 -0
- package/bundle/hkp-python/src/hkp/data.py +118 -0
- package/bundle/hkp-python/src/hkp/egress.py +310 -0
- package/bundle/hkp-python/src/hkp/mount.py +46 -0
- package/bundle/hkp-python/src/hkp/mounts.py +288 -0
- package/bundle/hkp-python/src/hkp/origins.py +169 -0
- package/bundle/hkp-python/src/hkp/runtime.py +1131 -0
- package/bundle/hkp-python/src/hkp/secrets.py +261 -0
- package/bundle/hkp-python/src/hkp/server.py +1714 -0
- package/bundle/hkp-python/src/hkp/services/__init__.py +0 -0
- package/bundle/hkp-python/src/hkp/services/asset.py +123 -0
- package/bundle/hkp-python/src/hkp/services/audio_encode.py +190 -0
- package/bundle/hkp-python/src/hkp/services/expression.py +942 -0
- package/bundle/hkp-python/src/hkp/services/gather.py +266 -0
- package/bundle/hkp-python/src/hkp/services/hold.py +246 -0
- package/bundle/hkp-python/src/hkp/services/http_client.py +444 -0
- package/bundle/hkp-python/src/hkp/services/http_server.py +790 -0
- package/bundle/hkp-python/src/hkp/services/iterator.py +141 -0
- package/bundle/hkp-python/src/hkp/services/join.py +173 -0
- package/bundle/hkp-python/src/hkp/services/map_service.py +452 -0
- package/bundle/hkp-python/src/hkp/services/monitor.py +99 -0
- package/bundle/hkp-python/src/hkp/services/nested_pipeline.py +278 -0
- package/bundle/hkp-python/src/hkp/services/skill_router.py +388 -0
- package/bundle/hkp-python/src/hkp/services/speech_to_text.py +186 -0
- package/bundle/hkp-python/src/hkp/services/spread.py +114 -0
- package/bundle/hkp-python/src/hkp/services/stopper.py +59 -0
- package/bundle/hkp-python/src/hkp/services/sub_service.py +417 -0
- package/bundle/hkp-python/src/hkp/services/text_generation.py +498 -0
- package/bundle/hkp-python/src/hkp/services/text_to_speech.py +237 -0
- package/bundle/hkp-python/src/hkp/services/timer.py +267 -0
- package/bundle/hkp-python/src/hkp/services/tracks.py +251 -0
- package/bundle/hkp-python/src/hkp/types.py +412 -0
- package/bundle/hkp-python/src/hkp/ws_tickets.py +54 -0
- package/bundle/hkp-python/src/hkp/yas.py +192 -0
- package/bundle/hkp-rt/lib/CMakeLists.txt +193 -0
- package/bundle/hkp-rt/lib/hkp-bundles/CMakeLists.txt +55 -0
- package/bundle/hkp-rt/lib/hkp-bundles/plugin_register.cpp +14 -0
- package/bundle/hkp-rt/lib/include/app.h +201 -0
- package/bundle/hkp-rt/lib/include/assets.h +239 -0
- package/bundle/hkp-rt/lib/include/auth.h +167 -0
- package/bundle/hkp-rt/lib/include/call_report.h +197 -0
- package/bundle/hkp-rt/lib/include/coordinator_links.h +127 -0
- package/bundle/hkp-rt/lib/include/discovery_hooks.h +29 -0
- package/bundle/hkp-rt/lib/include/egress.h +379 -0
- package/bundle/hkp-rt/lib/include/files.h +275 -0
- package/bundle/hkp-rt/lib/include/log_entry.h +131 -0
- package/bundle/hkp-rt/lib/include/mounts.h +139 -0
- package/bundle/hkp-rt/lib/include/origins.h +363 -0
- package/bundle/hkp-rt/lib/include/registry.h +119 -0
- package/bundle/hkp-rt/lib/include/secrets.h +414 -0
- package/bundle/hkp-rt/lib/include/server.h +122 -0
- package/bundle/hkp-rt/lib/include/service.h +190 -0
- package/bundle/hkp-rt/lib/include/slot_store.h +41 -0
- package/bundle/hkp-rt/lib/include/standalone_config.h +253 -0
- package/bundle/hkp-rt/lib/include/types/audio.h +3 -0
- package/bundle/hkp-rt/lib/include/types/data.h +152 -0
- package/bundle/hkp-rt/lib/include/types/message.h +56 -0
- package/bundle/hkp-rt/lib/include/types/models.h +154 -0
- package/bundle/hkp-rt/lib/include/types/owns_me.h +19 -0
- package/bundle/hkp-rt/lib/include/types/ringbuffer.h +86 -0
- package/bundle/hkp-rt/lib/include/types/types.h +33 -0
- package/bundle/hkp-rt/lib/include/types/validation.h +205 -0
- package/bundle/hkp-rt/lib/src/address.h +88 -0
- package/bundle/hkp-rt/lib/src/app.cpp +591 -0
- package/bundle/hkp-rt/lib/src/assets.cpp +686 -0
- package/bundle/hkp-rt/lib/src/binary_frame.h +154 -0
- package/bundle/hkp-rt/lib/src/common/expression.h +1481 -0
- package/bundle/hkp-rt/lib/src/common/inja.h +36 -0
- package/bundle/hkp-rt/lib/src/common/link_socket.cpp +437 -0
- package/bundle/hkp-rt/lib/src/common/link_socket.h +97 -0
- package/bundle/hkp-rt/lib/src/common/realtime_wakeup.h +120 -0
- package/bundle/hkp-rt/lib/src/common/stream_broadcast.h +250 -0
- package/bundle/hkp-rt/lib/src/common/string_util.h +31 -0
- package/bundle/hkp-rt/lib/src/common/websocket_client_session.cpp +217 -0
- package/bundle/hkp-rt/lib/src/common/websocket_client_session.h +37 -0
- package/bundle/hkp-rt/lib/src/common/websocket_config.h +119 -0
- package/bundle/hkp-rt/lib/src/common/websocket_protocol.h +32 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server.cpp +82 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server.h +57 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_listener.cpp +115 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_listener.h +35 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_session.cpp +172 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_session.h +53 -0
- package/bundle/hkp-rt/lib/src/common.h +13 -0
- package/bundle/hkp-rt/lib/src/coordinator_links.cpp +1142 -0
- package/bundle/hkp-rt/lib/src/discovery/discovery.cpp +707 -0
- package/bundle/hkp-rt/lib/src/discovery/discovery.h +89 -0
- package/bundle/hkp-rt/lib/src/http/auth.cpp +569 -0
- package/bundle/hkp-rt/lib/src/http/front_door.cpp +518 -0
- package/bundle/hkp-rt/lib/src/http/front_door.h +131 -0
- package/bundle/hkp-rt/lib/src/http/server.cpp +1539 -0
- package/bundle/hkp-rt/lib/src/http/ws_tickets.h +125 -0
- package/bundle/hkp-rt/lib/src/mount.h +63 -0
- package/bundle/hkp-rt/lib/src/process_context.h +335 -0
- package/bundle/hkp-rt/lib/src/process_depth.h +20 -0
- package/bundle/hkp-rt/lib/src/registry.cpp +233 -0
- package/bundle/hkp-rt/lib/src/runtime.cpp +986 -0
- package/bundle/hkp-rt/lib/src/runtime.h +225 -0
- package/bundle/hkp-rt/lib/src/runtime_host.h +180 -0
- package/bundle/hkp-rt/lib/src/service.cpp +315 -0
- package/bundle/hkp-rt/lib/src/services/asset.h +185 -0
- package/bundle/hkp-rt/lib/src/services/audio_encode.cpp +372 -0
- package/bundle/hkp-rt/lib/src/services/audio_encode.h +97 -0
- package/bundle/hkp-rt/lib/src/services/cache.h +362 -0
- package/bundle/hkp-rt/lib/src/services/cache_subservices.h +463 -0
- package/bundle/hkp-rt/lib/src/services/core_audio.cpp +384 -0
- package/bundle/hkp-rt/lib/src/services/core_audio.h +22 -0
- package/bundle/hkp-rt/lib/src/services/core_input.h +337 -0
- package/bundle/hkp-rt/lib/src/services/core_output.h +370 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.cpp +749 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.h +29 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.cpp +193 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.h +40 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/types.h +120 -0
- package/bundle/hkp-rt/lib/src/services/fft_service.h +190 -0
- package/bundle/hkp-rt/lib/src/services/filesystem.h +309 -0
- package/bundle/hkp-rt/lib/src/services/filter.h +208 -0
- package/bundle/hkp-rt/lib/src/services/gather.h +272 -0
- package/bundle/hkp-rt/lib/src/services/hold.h +304 -0
- package/bundle/hkp-rt/lib/src/services/http_client.cpp +347 -0
- package/bundle/hkp-rt/lib/src/services/http_client.h +67 -0
- package/bundle/hkp-rt/lib/src/services/http_client_impl.h +45 -0
- package/bundle/hkp-rt/lib/src/services/http_client_template_examples.md +202 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_listener.cpp +130 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_listener.h +33 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server.cpp +351 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server.h +79 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.cpp +230 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.h +64 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.cpp +950 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.h +253 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_session.cpp +495 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_session.h +169 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.cpp +270 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.h +104 -0
- package/bundle/hkp-rt/lib/src/services/http_server/request_decode.h +164 -0
- package/bundle/hkp-rt/lib/src/services/if_service.h +210 -0
- package/bundle/hkp-rt/lib/src/services/ifft_service.h +198 -0
- package/bundle/hkp-rt/lib/src/services/injector.h +162 -0
- package/bundle/hkp-rt/lib/src/services/iterator.h +201 -0
- package/bundle/hkp-rt/lib/src/services/map.h +641 -0
- package/bundle/hkp-rt/lib/src/services/monitor.h +161 -0
- package/bundle/hkp-rt/lib/src/services/mp3_frames.h +119 -0
- package/bundle/hkp-rt/lib/src/services/mp4_to_wav.cpp +281 -0
- package/bundle/hkp-rt/lib/src/services/mp4_to_wav.h +263 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.cpp +88 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.h +43 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server.h +163 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.cpp +133 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.h +46 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.cpp +165 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.h +44 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.cpp +184 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.h +55 -0
- package/bundle/hkp-rt/lib/src/services/receiver_session.h +172 -0
- package/bundle/hkp-rt/lib/src/services/root_certificates.h +3959 -0
- package/bundle/hkp-rt/lib/src/services/sha256_openssl.h +49 -0
- package/bundle/hkp-rt/lib/src/services/speech_http.h +306 -0
- package/bundle/hkp-rt/lib/src/services/speech_to_text.cpp +595 -0
- package/bundle/hkp-rt/lib/src/services/speech_to_text.h +69 -0
- package/bundle/hkp-rt/lib/src/services/static.cpp +45 -0
- package/bundle/hkp-rt/lib/src/services/static.h +41 -0
- package/bundle/hkp-rt/lib/src/services/stopper.h +72 -0
- package/bundle/hkp-rt/lib/src/services/sub_service.h +286 -0
- package/bundle/hkp-rt/lib/src/services/text_generation.cpp +1132 -0
- package/bundle/hkp-rt/lib/src/services/text_generation.h +71 -0
- package/bundle/hkp-rt/lib/src/services/text_to_speech.cpp +853 -0
- package/bundle/hkp-rt/lib/src/services/text_to_speech.h +97 -0
- package/bundle/hkp-rt/lib/src/services/timer.h +300 -0
- package/bundle/hkp-rt/lib/src/services/tracks.h +397 -0
- package/bundle/hkp-rt/lib/src/services/tts_inflect.cpp +149 -0
- package/bundle/hkp-rt/lib/src/services/tts_inflect.h +82 -0
- package/bundle/hkp-rt/lib/src/services/wav_reader.h +213 -0
- package/bundle/hkp-rt/lib/src/services/websocket_client_service.h +182 -0
- package/bundle/hkp-rt/lib/src/services/websocket_reader.h +148 -0
- package/bundle/hkp-rt/lib/src/services/websocket_server_service.h +196 -0
- package/bundle/hkp-rt/lib/src/services/websocket_writer.cpp +641 -0
- package/bundle/hkp-rt/lib/src/services/websocket_writer.h +117 -0
- package/bundle/hkp-rt/lib/src/sub_runtime.cpp +234 -0
- package/bundle/hkp-rt/lib/src/sub_runtime.h +160 -0
- package/bundle/hkp-rt/lib/src/types/data.cpp +237 -0
- package/bundle/hkp-rt/lib/src/types/message.cpp +148 -0
- package/bundle/hkp-rt/lib/src/types/models.cpp +83 -0
- package/bundle/hkp-rt/lib/src/types/ringbuffer.cpp +336 -0
- package/bundle/hkp-rt/lib/src/uuid.h +12 -0
- package/bundle/readymade-mcp-commits.json +5 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +79 -0
- package/dist/git.d.ts +8 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +53 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +130 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/repository.d.ts +49 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +452 -0
- package/dist/schemas.d.ts +156 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +43 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +83 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/validation.d.ts +9 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +101 -0
- package/package.json +52 -4
|
@@ -0,0 +1,1539 @@
|
|
|
1
|
+
#include "server.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <chrono>
|
|
5
|
+
#include <map>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <set>
|
|
8
|
+
|
|
9
|
+
#include <crow.h>
|
|
10
|
+
#include "origins.h"
|
|
11
|
+
|
|
12
|
+
#include <app.h>
|
|
13
|
+
#include <auth.h>
|
|
14
|
+
#include <types/validation.h>
|
|
15
|
+
|
|
16
|
+
#include "common/websocket_protocol.h"
|
|
17
|
+
#include "discovery/discovery.h"
|
|
18
|
+
#include "uuid.h"
|
|
19
|
+
#include "process_context.h"
|
|
20
|
+
#include "http/front_door.h"
|
|
21
|
+
#include "http/ws_tickets.h"
|
|
22
|
+
|
|
23
|
+
#include <atomic>
|
|
24
|
+
#include <thread>
|
|
25
|
+
|
|
26
|
+
namespace hkp
|
|
27
|
+
{
|
|
28
|
+
|
|
29
|
+
// Which runtime server this is, reported beside the runtimes so a client can
|
|
30
|
+
// tell remote runtimes apart without reading their address.
|
|
31
|
+
constexpr const char* kRuntimeServerKind = "c++";
|
|
32
|
+
|
|
33
|
+
// Per-connection bookkeeping for a notification WebSocket, stored as the Crow
|
|
34
|
+
// connection's userdata. `runtimeId` is empty until the client sends its
|
|
35
|
+
// protocol handshake ({type, id}); after that the connection is registered
|
|
36
|
+
// against that runtime and forwards/receives its messages.
|
|
37
|
+
struct WsConnState
|
|
38
|
+
{
|
|
39
|
+
std::string runtimeId;
|
|
40
|
+
std::string type; // "writer" | "reader" | "readwrite"
|
|
41
|
+
// Whoever opened the socket, when a token said so; nobody for a socket let
|
|
42
|
+
// in without one (no-auth mode, the local machine).
|
|
43
|
+
Caller caller;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Who a verified token speaks for, to a run they begin. The token's `sub` is
|
|
47
|
+
// the identity; hkp-rt only ever authorizes a verified, allow-listed email.
|
|
48
|
+
// A client holding a token is somebody this runtime is run for, so its
|
|
49
|
+
// owner; a member arrives through a coordinator, which says so.
|
|
50
|
+
inline Caller callerOf(const std::optional<Principal>& principal)
|
|
51
|
+
{
|
|
52
|
+
Caller caller;
|
|
53
|
+
if (principal && !principal->sub.empty())
|
|
54
|
+
{
|
|
55
|
+
caller.sub = principal->sub;
|
|
56
|
+
caller.email = principal->email;
|
|
57
|
+
caller.role = "owner";
|
|
58
|
+
}
|
|
59
|
+
return caller;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Crow middleware that gates every route on the runtime's Authenticator.
|
|
63
|
+
// In no-auth mode (loopback bind) it is a pass-through. CORS preflight
|
|
64
|
+
// (OPTIONS) requests carry no Authorization header and must never be gated.
|
|
65
|
+
// Extracts the token from an "Authorization: Bearer <token>" header value.
|
|
66
|
+
// Returns empty when the header is missing or not a bearer credential.
|
|
67
|
+
inline std::string bearerToken(const std::string& header)
|
|
68
|
+
{
|
|
69
|
+
constexpr const char* prefix = "Bearer ";
|
|
70
|
+
constexpr size_t prefixLen = 7;
|
|
71
|
+
if (header.size() <= prefixLen || header.compare(0, prefixLen, prefix) != 0)
|
|
72
|
+
{
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
return header.substr(prefixLen);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
inline bool forwarded(const crow::request& req)
|
|
79
|
+
{
|
|
80
|
+
return isForwarded([&req](const char* name) { return req.get_header_value(name); });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Where a request came from. Behind the front door every request arrives from
|
|
84
|
+
// this process, which says who the caller was in a header — believed only
|
|
85
|
+
// beside the secret the front door alone knows. See http/front_door.h. A
|
|
86
|
+
// request another proxy says it passed on is from nowhere in particular, and
|
|
87
|
+
// so never from this machine: behind nginx or Caddy on the same host every
|
|
88
|
+
// caller would otherwise be a loopback one (origins.h).
|
|
89
|
+
inline std::string callerAddress(const crow::request& req, const std::string& frontSecret)
|
|
90
|
+
{
|
|
91
|
+
if (forwarded(req))
|
|
92
|
+
{
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
if (!frontSecret.empty() &&
|
|
96
|
+
req.get_header_value(FrontDoor::FRONT_HEADER) == frontSecret)
|
|
97
|
+
{
|
|
98
|
+
return req.get_header_value(FrontDoor::CLIENT_HEADER);
|
|
99
|
+
}
|
|
100
|
+
return req.remote_ip_address;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// What the server knows about who may call it from a browser: the origins it
|
|
104
|
+
// was told, and the names it answers to. Shared by the middleware and the
|
|
105
|
+
// WebSocket accept, and changed while requests are being served.
|
|
106
|
+
struct OriginGate
|
|
107
|
+
{
|
|
108
|
+
AllowedOrigins allowed() const
|
|
109
|
+
{
|
|
110
|
+
std::lock_guard<std::mutex> lock(m_mutex);
|
|
111
|
+
return m_allowed;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
void setAllowed(AllowedOrigins allowed)
|
|
115
|
+
{
|
|
116
|
+
std::lock_guard<std::mutex> lock(m_mutex);
|
|
117
|
+
m_allowed = std::move(allowed);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void add(const std::vector<std::string>& origins)
|
|
121
|
+
{
|
|
122
|
+
std::lock_guard<std::mutex> lock(m_mutex);
|
|
123
|
+
m_allowed.add(origins);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
void addOwnName(const std::string& name)
|
|
127
|
+
{
|
|
128
|
+
std::lock_guard<std::mutex> lock(m_mutex);
|
|
129
|
+
m_ownNames.push_back(name);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Whether a request carrying no credential may be let in; see origins.h.
|
|
133
|
+
bool admits(const crow::request& req) const
|
|
134
|
+
{
|
|
135
|
+
std::lock_guard<std::mutex> lock(m_mutex);
|
|
136
|
+
return admitsWithoutCredential(
|
|
137
|
+
RequestSource{req.get_header_value("Origin"),
|
|
138
|
+
req.get_header_value("Sec-Fetch-Site"),
|
|
139
|
+
req.get_header_value("Host"),
|
|
140
|
+
forwarded(req)},
|
|
141
|
+
m_allowed, m_ownNames);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private:
|
|
145
|
+
mutable std::mutex m_mutex;
|
|
146
|
+
AllowedOrigins m_allowed;
|
|
147
|
+
std::vector<std::string> m_ownNames;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
struct AuthMiddleware
|
|
151
|
+
{
|
|
152
|
+
struct context
|
|
153
|
+
{
|
|
154
|
+
// Set when a bearer token was verified for this request. Left empty for
|
|
155
|
+
// every request let in some other way — no-auth mode, the local machine, a
|
|
156
|
+
// scoped capability grant — which is a request with nobody to name.
|
|
157
|
+
std::optional<Principal> principal;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Set once mounts are enabled; see callerAddress.
|
|
161
|
+
std::string frontSecret;
|
|
162
|
+
|
|
163
|
+
Authenticator* authenticator = nullptr;
|
|
164
|
+
CapabilityStore* capabilities = nullptr;
|
|
165
|
+
OriginGate* origins = nullptr;
|
|
166
|
+
|
|
167
|
+
void before_handle(crow::request& req, crow::response& res, context& ctx)
|
|
168
|
+
{
|
|
169
|
+
if (!authenticator)
|
|
170
|
+
{
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (req.method == crow::HTTPMethod::Options)
|
|
174
|
+
{
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// WebSocket upgrades are authenticated in the WS onaccept handler instead:
|
|
178
|
+
// a browser cannot set an Authorization header on a handshake, so it
|
|
179
|
+
// presents a ?ticket= bought with its token (ws_tickets.h). Gating here
|
|
180
|
+
// would 401 every notification socket.
|
|
181
|
+
if (req.upgrade)
|
|
182
|
+
{
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const bool noAuth = authenticator->isNoAuth();
|
|
186
|
+
// The local machine is trusted, even when the runtime is bound to 0.0.0.0
|
|
187
|
+
// for LAN access: the loopback interface cannot be reached from off-host.
|
|
188
|
+
// This lets the owner drive (and start discovery on) their own runtime
|
|
189
|
+
// without having to add themselves to the allow-list; only genuine LAN
|
|
190
|
+
// peers are challenged for a token.
|
|
191
|
+
//
|
|
192
|
+
// A page in the owner's browser is on this machine too, though, and so is
|
|
193
|
+
// every site it has open. Being local therefore lets a request in only
|
|
194
|
+
// when it does not come from a foreign page; see origins.h.
|
|
195
|
+
if (noAuth || isLoopbackHost(callerAddress(req, frontSecret)))
|
|
196
|
+
{
|
|
197
|
+
if (!origins || origins->admits(req))
|
|
198
|
+
{
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (noAuth)
|
|
202
|
+
{
|
|
203
|
+
// Nothing else could let it in. Answered without CORS headers (see
|
|
204
|
+
// CorsMiddleware), so the page that sent it can read neither this nor
|
|
205
|
+
// that anything answered: what a server that is not there looks like.
|
|
206
|
+
res.code = 403;
|
|
207
|
+
res.end();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// With auth configured, a request refused as a local one may still
|
|
211
|
+
// carry a credential, and is asked for it like any other.
|
|
212
|
+
}
|
|
213
|
+
// Scoped capability tokens are checked before JWT, exactly like an opaque
|
|
214
|
+
// token — but bound to a single method+path. For example, a phone that
|
|
215
|
+
// scanned a freshly minted QR can POST to its one upload runtime and nothing
|
|
216
|
+
// else; a token presented for any other route (or to mint more tokens) fails
|
|
217
|
+
// the scope match here and falls through to the JWT path, which denies it.
|
|
218
|
+
if (capabilities &&
|
|
219
|
+
capabilities->authorize(bearerToken(req.get_header_value("Authorization")),
|
|
220
|
+
crow::method_name(req.method), req.url))
|
|
221
|
+
{
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const auto result = authenticator->authorize(req.get_header_value("Authorization"));
|
|
225
|
+
if (result.status == AuthStatus::Ok)
|
|
226
|
+
{
|
|
227
|
+
ctx.principal = result.principal;
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
res.code = (result.status == AuthStatus::Forbidden) ? 403 : 401;
|
|
231
|
+
res.end();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
void after_handle(crow::request&, crow::response&, context&) {}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Says which page may read an answer and send a request that has to be asked
|
|
238
|
+
// for first: the page that is asking, when it is one this server allows.
|
|
239
|
+
//
|
|
240
|
+
// One place states it for every response — a handler's, an error's — so that
|
|
241
|
+
// none can answer a page the server does not allow.
|
|
242
|
+
//
|
|
243
|
+
// A preflight is the exception. Crow answers one as soon as it has read the
|
|
244
|
+
// request line, before the headers that say which page is asking, so it is
|
|
245
|
+
// answered the same for every page: yes, the request may be sent. That gives a
|
|
246
|
+
// foreign page nothing — the request it then sends states its origin and is
|
|
247
|
+
// refused on it, exactly as one that needs no preflight is.
|
|
248
|
+
struct CorsMiddleware
|
|
249
|
+
{
|
|
250
|
+
struct context {};
|
|
251
|
+
|
|
252
|
+
OriginGate* origins = nullptr;
|
|
253
|
+
Authenticator* authenticator = nullptr;
|
|
254
|
+
|
|
255
|
+
void before_handle(crow::request&, crow::response&, context&) {}
|
|
256
|
+
|
|
257
|
+
void after_handle(crow::request& req, crow::response& res, context&)
|
|
258
|
+
{
|
|
259
|
+
if (req.method == crow::HTTPMethod::Options)
|
|
260
|
+
{
|
|
261
|
+
res.set_header("Access-Control-Allow-Origin", "*");
|
|
262
|
+
allowRequest(res);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const auto origin = req.get_header_value("Origin");
|
|
266
|
+
if (origin.empty() || !origins)
|
|
267
|
+
{
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// A server without auth takes no credentials, so `*` — any page, with
|
|
271
|
+
// one — allows nobody there.
|
|
272
|
+
const auto allowed = origins->allowed();
|
|
273
|
+
const bool noAuth = !authenticator || authenticator->isNoAuth();
|
|
274
|
+
if (noAuth ? !allowed.allowsWithoutCredential(origin) : !allowed.allows(origin))
|
|
275
|
+
{
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
res.set_header("Access-Control-Allow-Origin", origin);
|
|
279
|
+
res.add_header("Vary", "Origin");
|
|
280
|
+
allowRequest(res);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
static void allowRequest(crow::response& res)
|
|
284
|
+
{
|
|
285
|
+
res.set_header("Access-Control-Allow-Methods", "*");
|
|
286
|
+
res.set_header("Access-Control-Allow-Headers",
|
|
287
|
+
"Content-Type, Authorization, Content-Disposition, "
|
|
288
|
+
"X-Upload-Id, X-Chunk-Index, X-Total-Chunks");
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
using CrowApp = crow::Crow<CorsMiddleware, AuthMiddleware>;
|
|
293
|
+
|
|
294
|
+
struct JsonResponse : crow::response
|
|
295
|
+
{
|
|
296
|
+
explicit JsonResponse(const json &_body)
|
|
297
|
+
: crow::response{_body.dump()}
|
|
298
|
+
{
|
|
299
|
+
add_header("Access-Control-Allow-Headers",
|
|
300
|
+
"Content-Type, Authorization, Content-Disposition, "
|
|
301
|
+
"X-Upload-Id, X-Chunk-Index, X-Total-Chunks");
|
|
302
|
+
add_header("Content-Type", "application/json");
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
struct Server::impl
|
|
306
|
+
{
|
|
307
|
+
impl(std::shared_ptr<App> a, const std::string& n, const std::string& ao = "*",
|
|
308
|
+
const std::string& dn = "", AuthConfig ac = {})
|
|
309
|
+
: app(a)
|
|
310
|
+
, name(n)
|
|
311
|
+
, allowedOrigins(ao)
|
|
312
|
+
, displayName(dn)
|
|
313
|
+
, authenticator(std::make_unique<Authenticator>(std::move(ac)))
|
|
314
|
+
{
|
|
315
|
+
originGate.setAllowed(AllowedOrigins::parse(allowedOrigins));
|
|
316
|
+
setupRoutes();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Friendly name shown to peers during discovery. Platforms that have a better
|
|
320
|
+
// name than the hostname (e.g. iOS, UIDevice.name) pass it in; otherwise fall
|
|
321
|
+
// back to the machine hostname.
|
|
322
|
+
std::string discoveryName() const
|
|
323
|
+
{
|
|
324
|
+
return displayName.empty() ? discoveryDeviceName() : displayName;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
void setupRoutes()
|
|
328
|
+
{
|
|
329
|
+
auto& cors = crow.get_middleware<CorsMiddleware>();
|
|
330
|
+
cors.origins = &originGate;
|
|
331
|
+
cors.authenticator = authenticator.get();
|
|
332
|
+
|
|
333
|
+
auto& authMiddleware = crow.get_middleware<AuthMiddleware>();
|
|
334
|
+
authMiddleware.authenticator = authenticator.get();
|
|
335
|
+
authMiddleware.capabilities = &capabilities;
|
|
336
|
+
authMiddleware.origins = &originGate;
|
|
337
|
+
|
|
338
|
+
// A ticket for the notification socket, minted for whoever this request
|
|
339
|
+
// was let in as, so the socket is opened without a token in its address.
|
|
340
|
+
CROW_ROUTE(crow, "/ws-tickets")
|
|
341
|
+
.methods("POST"_method)([this](const crow::request& req) {
|
|
342
|
+
const auto ticket = wsTickets.mint(callerOfRequest(req));
|
|
343
|
+
if (ticket.empty())
|
|
344
|
+
{
|
|
345
|
+
return crow::response(503);
|
|
346
|
+
}
|
|
347
|
+
crow::response res(json{{"ticket", ticket},
|
|
348
|
+
{"expiresInMs", std::chrono::milliseconds(WsTickets<Caller>::TTL).count()}}
|
|
349
|
+
.dump());
|
|
350
|
+
res.set_header("Content-Type", "application/json");
|
|
351
|
+
return res;
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
CROW_ROUTE(crow, "/runtimes")
|
|
355
|
+
.methods("GET"_method)([this]() { return getRuntimes(); });
|
|
356
|
+
|
|
357
|
+
CROW_ROUTE(crow, "/runtimes")
|
|
358
|
+
.methods("DELETE"_method)([this](const crow::request &req) { return deleteAllRuntimes(); });
|
|
359
|
+
|
|
360
|
+
CROW_ROUTE(crow, "/runtimes")
|
|
361
|
+
.methods("POST"_method)([this](const crow::request &req) -> crow::response { return createRuntimes(req); });
|
|
362
|
+
|
|
363
|
+
CROW_ROUTE(crow, "/runtimes/<string>")
|
|
364
|
+
.methods("GET"_method)([this](const crow::request &req, std::string id) { return getRuntimeById(id); });
|
|
365
|
+
|
|
366
|
+
CROW_ROUTE(crow, "/runtimes/<string>")
|
|
367
|
+
.methods("DELETE"_method)([this](const crow::request &req, std::string id) { return deleteRuntime(id); });
|
|
368
|
+
|
|
369
|
+
// Values for the references this runtime's services hold.
|
|
370
|
+
//
|
|
371
|
+
// Provisioning carries them already; this is for the moments it cannot
|
|
372
|
+
// cover — a board being built a service at a time, an entry edited while a
|
|
373
|
+
// board is running, and a re-push after a restart where the services
|
|
374
|
+
// survived but the vault did not. It merges, so a client sending one entry
|
|
375
|
+
// does not strip the rest.
|
|
376
|
+
//
|
|
377
|
+
// POST rather than PUT: it merges rather than replaces, and every other
|
|
378
|
+
// mutation this server takes is a POST — a lone PUT is a method each
|
|
379
|
+
// runtime implementation would have to remember to allow through CORS.
|
|
380
|
+
//
|
|
381
|
+
// There is deliberately no GET. The values go one way: in, and then only to
|
|
382
|
+
// a service resolving a reference for a call it is making. What is held can
|
|
383
|
+
// be *named* — the response says which aliases the runtime now has —
|
|
384
|
+
// because a client needs to show whether a credential is configured.
|
|
385
|
+
CROW_ROUTE(crow, "/runtimes/<string>/secrets")
|
|
386
|
+
.methods("POST"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return setSecrets(req, runtimeId); });
|
|
387
|
+
|
|
388
|
+
// Descriptors for the assets this runtime's services reference. Provisioning
|
|
389
|
+
// carries them already; this is for a configuration naming one the runtime
|
|
390
|
+
// was not given, for an asset edited while the board runs — which is how an
|
|
391
|
+
// edit reaches a service without reconfiguring it — and for a re-push after
|
|
392
|
+
// a restart. It merges, and null removes an asset. Answers with ids, never
|
|
393
|
+
// content.
|
|
394
|
+
CROW_ROUTE(crow, "/runtimes/<string>/assets")
|
|
395
|
+
.methods("POST"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return setAssets(req, runtimeId); });
|
|
396
|
+
|
|
397
|
+
// Whether an asset resolves here, and to what: a check, not a download.
|
|
398
|
+
CROW_ROUTE(crow, "/runtimes/<string>/assets/<string>")
|
|
399
|
+
.methods("GET"_method)([this](const crow::request &, std::string runtimeId, std::string assetId) -> crow::response {
|
|
400
|
+
auto checked = app->checkRuntimeAsset(runtimeId, assetId);
|
|
401
|
+
if (checked.is_null())
|
|
402
|
+
{
|
|
403
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
404
|
+
}
|
|
405
|
+
return makeJsonResponse(checked);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Change what a running runtime records, without rebuilding it. Separate
|
|
409
|
+
// from POST /runtimes/<id>, which processes data rather than configuring
|
|
410
|
+
// anything.
|
|
411
|
+
CROW_ROUTE(crow, "/runtimes/<string>/state")
|
|
412
|
+
.methods("PATCH"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return setRuntimeState(req, runtimeId); });
|
|
413
|
+
|
|
414
|
+
CROW_ROUTE(crow, "/runtimes/<string>/rearrange")
|
|
415
|
+
.methods("POST"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return rearrangeServices(req, runtimeId); });
|
|
416
|
+
|
|
417
|
+
CROW_ROUTE(crow, "/runtimes/<string>")
|
|
418
|
+
.methods("POST"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return processRuntime(req, runtimeId); });
|
|
419
|
+
|
|
420
|
+
// A person's own client introducing this server to a coordinator, for one
|
|
421
|
+
// runtime of one board: it has asked the coordinator for a ticket and
|
|
422
|
+
// passes it on. This server then connects to the coordinator — the
|
|
423
|
+
// coordinator connects to nothing — and keeps the ticket to reconnect with.
|
|
424
|
+
CROW_ROUTE(crow, "/coordinator-links")
|
|
425
|
+
.methods("POST"_method)([this](const crow::request &req) -> crow::response { return introduceCoordinator(req); });
|
|
426
|
+
|
|
427
|
+
// The links held: which runtimes belong to which board, never a ticket.
|
|
428
|
+
CROW_ROUTE(crow, "/coordinator-links")
|
|
429
|
+
.methods("GET"_method)([this]() -> crow::response {
|
|
430
|
+
return makeJsonResponse(json{{"links", coordinatorLinks ? coordinatorLinks->list() : json::array()}});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Leaves a board: drops the link and the runtime it was for.
|
|
434
|
+
CROW_ROUTE(crow, "/coordinator-links/<string>/<string>")
|
|
435
|
+
.methods("DELETE"_method)([this](const crow::request &req, std::string boardName, std::string runtimeId) -> crow::response {
|
|
436
|
+
const bool removed = coordinatorLinks && coordinatorLinks->remove(boardName, runtimeId);
|
|
437
|
+
return crow::response{removed ? crow::status::OK : crow::status::NOT_FOUND};
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
CROW_ROUTE(crow, "/runtimes/<string>/inputs")
|
|
441
|
+
.methods("GET"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return getRuntimeInputs(req, runtimeId); });
|
|
442
|
+
|
|
443
|
+
CROW_ROUTE(crow, "/runtimes/<string>/inputs/<string>")
|
|
444
|
+
.methods("GET"_method)([this](const crow::request &req, std::string runtimeId, std::string inputId) -> crow::response { return getRuntimeInput(req, runtimeId, inputId); });
|
|
445
|
+
|
|
446
|
+
CROW_ROUTE(crow, "/runtimes/<string>/services/<string>")
|
|
447
|
+
.methods("POST"_method)([this](const crow::request &req, std::string runtimeId, std::string instanceId) -> crow::response { return configureService(req, runtimeId, instanceId); });
|
|
448
|
+
|
|
449
|
+
CROW_ROUTE(crow, "/runtimes/<string>/services/<string>/process")
|
|
450
|
+
.methods("POST"_method)([this](const crow::request &req, std::string runtimeId, std::string instanceId) -> crow::response { return processService(req, runtimeId, instanceId); });
|
|
451
|
+
|
|
452
|
+
CROW_ROUTE(crow, "/runtimes/<string>/services/<string>")
|
|
453
|
+
.methods("GET"_method)([this](const crow::request &req, std::string runtimeId, std::string instanceId) -> crow::response { return getServiceState(runtimeId, instanceId); });
|
|
454
|
+
|
|
455
|
+
CROW_ROUTE(crow, "/runtimes/<string>/services/<string>/property/<string>")
|
|
456
|
+
.methods("GET"_method)([this](const crow::request &req, std::string runtimeId, std::string instanceId, std::string propertyId) -> crow::response { return getServiceStateProperty(runtimeId, instanceId, propertyId); });
|
|
457
|
+
|
|
458
|
+
CROW_ROUTE(crow, "/runtimes/<string>/services")
|
|
459
|
+
.methods("GET"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return getServices(req, runtimeId); });
|
|
460
|
+
|
|
461
|
+
// TODO this should be a PUT and the POST should replace all services
|
|
462
|
+
CROW_ROUTE(crow, "/runtimes/<string>/services")
|
|
463
|
+
.methods("POST"_method)([this](const crow::request &req, std::string runtimeId) -> crow::response { return createService(req, runtimeId); });
|
|
464
|
+
|
|
465
|
+
CROW_ROUTE(crow, "/runtimes/<string>/services/<string>")
|
|
466
|
+
.methods("DELETE"_method)([this](const crow::request &req, std::string runtimeId, std::string instanceId) -> crow::response { return deleteService(runtimeId, instanceId); });
|
|
467
|
+
|
|
468
|
+
// ── LAN discovery ──
|
|
469
|
+
CROW_ROUTE(crow, "/discover")
|
|
470
|
+
.methods("POST"_method)([this](const crow::request &req) { return startDiscover(req); });
|
|
471
|
+
|
|
472
|
+
CROW_ROUTE(crow, "/discover")
|
|
473
|
+
.methods("GET"_method)([this]() { return getDiscover(); });
|
|
474
|
+
|
|
475
|
+
CROW_ROUTE(crow, "/discover")
|
|
476
|
+
.methods("DELETE"_method)([this]() { return stopDiscover(); });
|
|
477
|
+
|
|
478
|
+
CROW_ROUTE(crow, "/identity")
|
|
479
|
+
.methods("GET"_method)([this]() { return getIdentity(); });
|
|
480
|
+
|
|
481
|
+
// ── Notification WebSocket ──
|
|
482
|
+
// One socket for every runtime in this process; connections bind to a
|
|
483
|
+
// runtime via their protocol handshake. Authenticated in onaccept (the HTTP
|
|
484
|
+
// AuthMiddleware deliberately skips upgrades).
|
|
485
|
+
CROW_WEBSOCKET_ROUTE(crow, "/notifications")
|
|
486
|
+
.onaccept([this](const crow::request& req, void** userdata) { return wsOnAccept(req, userdata); })
|
|
487
|
+
.onmessage([this](crow::websocket::connection& conn, const std::string& message, bool isBinary) { wsOnMessage(conn, message, isBinary); })
|
|
488
|
+
.onclose([this](crow::websocket::connection& conn, const std::string& /*reason*/, uint16_t /*code*/) { wsOnClose(conn); });
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
crow::response getRuntimes();
|
|
492
|
+
crow::response deleteRuntime(const std::string& runtimeId);
|
|
493
|
+
crow::response deleteAllRuntimes();
|
|
494
|
+
crow::response createRuntimes(const crow::request &req);
|
|
495
|
+
crow::response configureService(const crow::request &req, const std::string& runtimeId, const std::string& instanceId);
|
|
496
|
+
crow::response getServiceState(const std::string& runtimeId, const std::string& instanceId);
|
|
497
|
+
crow::response getServiceStateProperty(const std::string& runtimeId, const std::string& instanceId, const std::string& propertyId);
|
|
498
|
+
crow::response getServices(const crow::request &req, const std::string& runtimeId);
|
|
499
|
+
crow::response createService(const crow::request &req, const std::string& runtimeId);
|
|
500
|
+
crow::response deleteService(const std::string& runtimeId, const std::string& instanceId);
|
|
501
|
+
crow::response getRuntimeById(const std::string& id);
|
|
502
|
+
crow::response rearrangeServices(const crow::request &req, const std::string& runtimeId);
|
|
503
|
+
crow::response setSecrets(const crow::request &req, const std::string& runtimeId);
|
|
504
|
+
crow::response setAssets(const crow::request &req, const std::string& runtimeId);
|
|
505
|
+
crow::response setRuntimeState(const crow::request &req, const std::string& runtimeId);
|
|
506
|
+
crow::response introduceCoordinator(const crow::request &req);
|
|
507
|
+
crow::response processRuntime(const crow::request &req, const std::string& runtimeId);
|
|
508
|
+
// Who a request is from, when a token said so; see AuthMiddleware::context.
|
|
509
|
+
Caller callerOfRequest(const crow::request& req);
|
|
510
|
+
crow::response processService(const crow::request &req, const std::string& runtimeId, const std::string& instanceId);
|
|
511
|
+
crow::response getRuntimeInputs(const crow::request &req, const std::string& runtimeId);
|
|
512
|
+
crow::response getRuntimeInput(const crow::request &req, const std::string& runtimeId, const std::string& inputId);
|
|
513
|
+
|
|
514
|
+
// ── Notification WebSocket ──────────────────────────────────────────────────
|
|
515
|
+
bool wsOnAccept(const crow::request& req, void** userdata);
|
|
516
|
+
void wsOnMessage(crow::websocket::connection& conn, const std::string& message, bool isBinary);
|
|
517
|
+
void wsOnClose(crow::websocket::connection& conn);
|
|
518
|
+
void reapIfAbandoned(const std::string& runtimeId);
|
|
519
|
+
void sendNotification(const std::string& runtimeId, const std::string& frame);
|
|
520
|
+
void sendText(const std::string& runtimeId, const std::string& text);
|
|
521
|
+
|
|
522
|
+
JsonResponse makeJsonResponse(const json &_body)
|
|
523
|
+
{
|
|
524
|
+
return JsonResponse(_body);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ── LAN discovery ──────────────────────────────────────────────────────────
|
|
528
|
+
// Advertising is only meaningful when the runtime is LAN-reachable (bound to
|
|
529
|
+
// 0.0.0.0); a localhost-only instance can still browse for peers.
|
|
530
|
+
json discoverState()
|
|
531
|
+
{
|
|
532
|
+
auto peers = json::array();
|
|
533
|
+
for (const auto& peer : discovery.peers())
|
|
534
|
+
{
|
|
535
|
+
peers.push_back(peer);
|
|
536
|
+
}
|
|
537
|
+
return json{
|
|
538
|
+
{"active", discovery.isActive()},
|
|
539
|
+
{"endsAt", discovery.endsAtEpochMs()},
|
|
540
|
+
{"peers", peers},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
crow::response startDiscover(const crow::request& req)
|
|
545
|
+
{
|
|
546
|
+
int seconds = 30;
|
|
547
|
+
if (!req.body.empty())
|
|
548
|
+
{
|
|
549
|
+
try
|
|
550
|
+
{
|
|
551
|
+
auto body = json::parse(req.body);
|
|
552
|
+
if (body.contains("durationSeconds") && body["durationSeconds"].is_number_integer())
|
|
553
|
+
{
|
|
554
|
+
seconds = body["durationSeconds"].get<int>();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
catch (...) { /* fall back to default */ }
|
|
558
|
+
}
|
|
559
|
+
seconds = std::clamp(seconds, 5, 120);
|
|
560
|
+
|
|
561
|
+
DiscoveryManager::Identity self;
|
|
562
|
+
self.id = instanceId;
|
|
563
|
+
self.name = discoveryName();
|
|
564
|
+
self.port = crow.port();
|
|
565
|
+
discovery.start(self, /*advertise=*/bindAddress == "0.0.0.0", seconds);
|
|
566
|
+
return makeJsonResponse(discoverState());
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
crow::response getDiscover()
|
|
570
|
+
{
|
|
571
|
+
return makeJsonResponse(discoverState());
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
crow::response stopDiscover()
|
|
575
|
+
{
|
|
576
|
+
discovery.stop();
|
|
577
|
+
return makeJsonResponse(discoverState());
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
crow::response getIdentity()
|
|
581
|
+
{
|
|
582
|
+
return makeJsonResponse(json{
|
|
583
|
+
{"id", instanceId},
|
|
584
|
+
{"name", discoveryName()},
|
|
585
|
+
{"platform", discoveryPlatformName()},
|
|
586
|
+
{"port", crow.port()},
|
|
587
|
+
{"exposed", bindAddress == "0.0.0.0"},
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
CrowApp crow;
|
|
592
|
+
std::string externalIP;
|
|
593
|
+
std::shared_ptr<App> app;
|
|
594
|
+
std::string name;
|
|
595
|
+
std::string allowedOrigins;
|
|
596
|
+
OriginGate originGate;
|
|
597
|
+
// What a browser opens the notification socket with instead of its token;
|
|
598
|
+
// see http/ws_tickets.h.
|
|
599
|
+
WsTickets<Caller> wsTickets;
|
|
600
|
+
std::string displayName;
|
|
601
|
+
std::string bindAddress;
|
|
602
|
+
std::string instanceId = generateUUID();
|
|
603
|
+
std::unique_ptr<Authenticator> authenticator;
|
|
604
|
+
// Set by a host that serves mounts; see Server::enableMounts.
|
|
605
|
+
std::unique_ptr<FrontDoor> frontDoor;
|
|
606
|
+
Server::MountOptions mountOptions;
|
|
607
|
+
std::atomic<unsigned int> publicPort{0};
|
|
608
|
+
// Set by a host that lets this server be introduced to coordinators.
|
|
609
|
+
std::unique_ptr<CoordinatorLinks> coordinatorLinks;
|
|
610
|
+
CapabilityStore capabilities;
|
|
611
|
+
DiscoveryManager discovery;
|
|
612
|
+
|
|
613
|
+
// runtimeId → live notification connections. Guarded by wsMutex because it is
|
|
614
|
+
// touched from Crow's IO thread (accept/message/close) and the App event-loop
|
|
615
|
+
// thread (sendNotification).
|
|
616
|
+
std::mutex wsMutex;
|
|
617
|
+
std::map<std::string, std::set<crow::websocket::connection*>> wsByRuntime;
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
Server::Server(
|
|
621
|
+
std::shared_ptr<App> app,
|
|
622
|
+
const std::string& name,
|
|
623
|
+
const std::string& allowedOrigins,
|
|
624
|
+
const std::string& displayName,
|
|
625
|
+
AuthConfig authConfig
|
|
626
|
+
) : m_impl(std::make_unique<impl>(app, name, allowedOrigins, displayName, std::move(authConfig)))
|
|
627
|
+
{
|
|
628
|
+
app->setServer(this);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
Server::~Server()
|
|
632
|
+
{
|
|
633
|
+
m_impl->app->setServer(nullptr);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
void Server::handleRequest(crow::request& req, crow::response& res)
|
|
637
|
+
{
|
|
638
|
+
m_impl->crow.handle_full(req, res);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
void Server::start(const std::string& externalIP, unsigned int port, const std::string& bindAddress)
|
|
642
|
+
{
|
|
643
|
+
m_impl->externalIP = externalIP;
|
|
644
|
+
// A name this server is reached by, when it was given one rather than an
|
|
645
|
+
// address; see isKnownHost.
|
|
646
|
+
m_impl->originGate.addOwnName(externalIP);
|
|
647
|
+
m_impl->bindAddress = bindAddress;
|
|
648
|
+
if (!m_impl->frontDoor)
|
|
649
|
+
{
|
|
650
|
+
m_impl->crow.bindaddr(bindAddress).port(port).run();
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// The front door takes the port. The api listens on loopback, on a port the
|
|
655
|
+
// OS picks and nobody is told, and is reached through the front door only.
|
|
656
|
+
m_impl->publicPort = port;
|
|
657
|
+
std::atomic<bool> failed{false};
|
|
658
|
+
auto* impl = m_impl.get();
|
|
659
|
+
std::thread opener([impl, &failed, bindAddress, port]() {
|
|
660
|
+
impl->crow.wait_for_server_start(std::chrono::milliseconds(30000));
|
|
661
|
+
const auto bound = impl->frontDoor->start(
|
|
662
|
+
bindAddress, static_cast<unsigned short>(port), impl->crow.port());
|
|
663
|
+
if (bound == 0)
|
|
664
|
+
{
|
|
665
|
+
failed = true;
|
|
666
|
+
impl->crow.stop();
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
impl->publicPort = bound;
|
|
670
|
+
});
|
|
671
|
+
m_impl->crow.bindaddr("127.0.0.1").port(0).run();
|
|
672
|
+
opener.join();
|
|
673
|
+
m_impl->frontDoor->stop();
|
|
674
|
+
if (failed)
|
|
675
|
+
{
|
|
676
|
+
throw std::runtime_error("could not listen on " + bindAddress + ":" + std::to_string(port));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
void Server::enableMounts(MountOptions options)
|
|
681
|
+
{
|
|
682
|
+
if (options.secret.empty())
|
|
683
|
+
{
|
|
684
|
+
options.secret = generateUUID() + generateUUID();
|
|
685
|
+
}
|
|
686
|
+
m_impl->mountOptions = std::move(options);
|
|
687
|
+
m_impl->originGate.addOwnName(m_impl->mountOptions.externalHost);
|
|
688
|
+
m_impl->publicPort = m_impl->mountOptions.port;
|
|
689
|
+
if (m_impl->externalIP.empty())
|
|
690
|
+
{
|
|
691
|
+
m_impl->externalIP = m_impl->mountOptions.externalHost;
|
|
692
|
+
}
|
|
693
|
+
// Known to this process only: what makes the caller's address, as the front
|
|
694
|
+
// door passes it on, something the api can believe.
|
|
695
|
+
m_impl->frontDoor = std::make_unique<FrontDoor>(generateUUID() + generateUUID());
|
|
696
|
+
m_impl->crow.get_middleware<AuthMiddleware>().frontSecret = m_impl->frontDoor->frontSecret();
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
MountHandle Server::mount(const std::string& boardName, const std::string& runtimeId,
|
|
700
|
+
const std::string& name, MountAdopter adopter,
|
|
701
|
+
bool deployed)
|
|
702
|
+
{
|
|
703
|
+
if (!m_impl->frontDoor)
|
|
704
|
+
{
|
|
705
|
+
return {};
|
|
706
|
+
}
|
|
707
|
+
const auto id = deriveMountId(m_impl->mountOptions.secret, "", boardName, runtimeId, name);
|
|
708
|
+
auto release = m_impl->frontDoor->mount(id, std::move(adopter), deployed);
|
|
709
|
+
const std::string base = !m_impl->mountOptions.externalUrl.empty()
|
|
710
|
+
? m_impl->mountOptions.externalUrl
|
|
711
|
+
: "http://" + m_impl->externalIP + ":" + std::to_string(port());
|
|
712
|
+
return MountHandle(name, base + MOUNT_PREFIX + "/" + id, std::move(release));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
void Server::enableCoordinatorLinks(std::shared_ptr<LinkStore> store,
|
|
716
|
+
CoordinatorLinksOptions options)
|
|
717
|
+
{
|
|
718
|
+
m_impl->coordinatorLinks = std::make_unique<CoordinatorLinks>(
|
|
719
|
+
m_impl->app, std::move(store), std::move(options));
|
|
720
|
+
m_impl->coordinatorLinks->restore();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
void Server::stop()
|
|
724
|
+
{
|
|
725
|
+
// Before the routes go: a link's connection is closed, its ticket kept.
|
|
726
|
+
if (m_impl->coordinatorLinks)
|
|
727
|
+
{
|
|
728
|
+
m_impl->coordinatorLinks->stop();
|
|
729
|
+
}
|
|
730
|
+
m_impl->crow.stop();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
unsigned int Server::port() const
|
|
734
|
+
{
|
|
735
|
+
// The port callers use, which with mounts is the front door's.
|
|
736
|
+
return m_impl->frontDoor ? m_impl->publicPort.load() : m_impl->crow.port();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const std::string& Server::externalIP() const
|
|
740
|
+
{
|
|
741
|
+
return m_impl->externalIP;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const std::string& Server::name() const
|
|
745
|
+
{
|
|
746
|
+
return m_impl->name;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const std::string& Server::allowedOrigins() const
|
|
750
|
+
{
|
|
751
|
+
return m_impl->allowedOrigins;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
void Server::allowOrigins(const std::vector<std::string>& origins)
|
|
755
|
+
{
|
|
756
|
+
m_impl->originGate.add(origins);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
crow::response Server::impl::getRuntimes()
|
|
760
|
+
{
|
|
761
|
+
auto arr = json::array();
|
|
762
|
+
for (auto& rt : app->getRuntimes())
|
|
763
|
+
{
|
|
764
|
+
arr.push_back(jsonSerialise(rt));
|
|
765
|
+
}
|
|
766
|
+
return makeJsonResponse(json{{"runtimes", arr}, {"registry", app->getRegistry()}, {"server", kRuntimeServerKind}, {"coordinatorLinks", coordinatorLinks != nullptr}, {"boardRuntimes", true}});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
crow::response Server::impl::deleteRuntime(const std::string& runtimeId)
|
|
771
|
+
{
|
|
772
|
+
// Idempotent: a runtime already gone is the desired end state, not an error.
|
|
773
|
+
// A client removing a runtime closes its notification socket first, and that
|
|
774
|
+
// close reaps a garbage-collected runtime on its own — so by the time this
|
|
775
|
+
// explicit DELETE arrives the runtime is frequently already removed. Returning
|
|
776
|
+
// NOT_FOUND there surfaces a spurious "Failed to remove runtime" to the user.
|
|
777
|
+
app->removeRuntime(runtimeId);
|
|
778
|
+
return makeJsonResponse(json{{"id", runtimeId}});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
crow::response Server::impl::deleteAllRuntimes()
|
|
782
|
+
{
|
|
783
|
+
app->removeAllRuntimes();
|
|
784
|
+
return crow::response{crow::status::OK};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
crow::response Server::impl::createRuntimes(const crow::request &req)
|
|
788
|
+
{
|
|
789
|
+
if (req.body.empty())
|
|
790
|
+
{
|
|
791
|
+
return crow::response(crow::status::BAD_REQUEST); // same as crow::response(400)
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
auto arr = json::array();
|
|
795
|
+
auto body = json::parse(req.body);
|
|
796
|
+
if (!body.is_array() && !body.is_object())
|
|
797
|
+
{
|
|
798
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
799
|
+
}
|
|
800
|
+
auto runtimeBody = body.is_array() ? body : json::array({body});
|
|
801
|
+
for (auto it : runtimeBody)
|
|
802
|
+
{
|
|
803
|
+
auto rtConfig = validateRuntime(it);
|
|
804
|
+
if (!rtConfig)
|
|
805
|
+
{
|
|
806
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
807
|
+
}
|
|
808
|
+
auto createdConfig = app->createRuntime(*rtConfig);
|
|
809
|
+
arr.push_back(jsonSerialise(createdConfig));
|
|
810
|
+
}
|
|
811
|
+
return makeJsonResponse({json{{"runtimes", arr}, {"registry", app->getRegistry()}, {"server", kRuntimeServerKind}, {"coordinatorLinks", coordinatorLinks != nullptr}, {"boardRuntimes", true}}});
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
crow::response Server::impl::configureService(const crow::request &req, const std::string& runtimeId, const std::string& instanceId)
|
|
815
|
+
{
|
|
816
|
+
if (req.body.empty())
|
|
817
|
+
{
|
|
818
|
+
return crow::response(crow::status::BAD_REQUEST); // same as crow::response(400)
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
auto body = json::parse(req.body);
|
|
822
|
+
if (!body.is_object())
|
|
823
|
+
{
|
|
824
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
825
|
+
}
|
|
826
|
+
const auto context = ProcessContext::forClient(json(), callerOfRequest(req));
|
|
827
|
+
auto config = app->configureService(runtimeId, instanceId, body, "", &context);
|
|
828
|
+
if (config.is_null())
|
|
829
|
+
{
|
|
830
|
+
return crow::response(crow::status::NOT_FOUND);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return makeJsonResponse({config});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
crow::response Server::impl::getServiceState(const std::string& runtimeId, const std::string& instanceId)
|
|
837
|
+
{
|
|
838
|
+
auto rt = app->getRuntime(runtimeId);
|
|
839
|
+
if (!rt)
|
|
840
|
+
{
|
|
841
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
842
|
+
}
|
|
843
|
+
auto state = app->getServiceState(runtimeId, instanceId);
|
|
844
|
+
if (state.is_null())
|
|
845
|
+
{
|
|
846
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
847
|
+
}
|
|
848
|
+
return makeJsonResponse({state});
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
crow::response Server::impl::getServiceStateProperty(const std::string& runtimeId, const std::string& instanceId, const std::string& propertyId)
|
|
852
|
+
{
|
|
853
|
+
auto rt = app->getRuntime(runtimeId);
|
|
854
|
+
if (!rt)
|
|
855
|
+
{
|
|
856
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
857
|
+
}
|
|
858
|
+
auto state = app->getServiceState(runtimeId, instanceId);
|
|
859
|
+
if (state.is_null())
|
|
860
|
+
{
|
|
861
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
862
|
+
}
|
|
863
|
+
auto property = state[propertyId];
|
|
864
|
+
if (property.is_null())
|
|
865
|
+
{
|
|
866
|
+
std::cout << "Propety not found in: " << state << std::endl;
|
|
867
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
868
|
+
}
|
|
869
|
+
return makeJsonResponse(property);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
crow::response Server::impl::getServices(const crow::request &req, const std::string& runtimeId)
|
|
873
|
+
{
|
|
874
|
+
auto rt = app->getRuntime(runtimeId);
|
|
875
|
+
if (!rt)
|
|
876
|
+
{
|
|
877
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
878
|
+
}
|
|
879
|
+
auto services = app->getServices(runtimeId);
|
|
880
|
+
return makeJsonResponse(services);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
crow::response Server::impl::createService(const crow::request &req, const std::string& runtimeId)
|
|
884
|
+
{
|
|
885
|
+
auto rt = app->getRuntime(runtimeId);
|
|
886
|
+
if (!rt)
|
|
887
|
+
{
|
|
888
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (req.body.empty())
|
|
892
|
+
{
|
|
893
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
auto arr = json::array();
|
|
897
|
+
auto body = json::parse(req.body);
|
|
898
|
+
if (body.is_null())
|
|
899
|
+
{
|
|
900
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
auto serviceConfig = validateService(body);
|
|
904
|
+
if (!serviceConfig)
|
|
905
|
+
{
|
|
906
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
auto res = app->appendService(runtimeId, *serviceConfig);
|
|
910
|
+
if (res.is_null())
|
|
911
|
+
{
|
|
912
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
return makeJsonResponse(res);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
crow::response Server::impl::deleteService(const std::string& runtimeId, const std::string& instanceId)
|
|
919
|
+
{
|
|
920
|
+
auto rt = app->getRuntime(runtimeId);
|
|
921
|
+
if (!rt)
|
|
922
|
+
{
|
|
923
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
auto res = app->removeService(runtimeId, instanceId);
|
|
927
|
+
return makeJsonResponse(res);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
crow::response Server::impl::getRuntimeById(const std::string& id)
|
|
931
|
+
{
|
|
932
|
+
auto rt = app->getRuntime(id);
|
|
933
|
+
if (!rt)
|
|
934
|
+
{
|
|
935
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
936
|
+
}
|
|
937
|
+
return makeJsonResponse(jsonSerialise(*rt));
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
crow::response Server::impl::introduceCoordinator(const crow::request &req)
|
|
941
|
+
{
|
|
942
|
+
if (!coordinatorLinks)
|
|
943
|
+
{
|
|
944
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
945
|
+
}
|
|
946
|
+
const json body = json::parse(req.body, nullptr, /*allow_exceptions=*/false);
|
|
947
|
+
const auto text = [&body](const char* key) {
|
|
948
|
+
return body.is_object() && body.contains(key) && body[key].is_string()
|
|
949
|
+
? body[key].get<std::string>() : std::string();
|
|
950
|
+
};
|
|
951
|
+
const LinkRecord record{text("boardName"), text("runtimeId"),
|
|
952
|
+
text("coordinatorUrl"), text("ticket")};
|
|
953
|
+
if (record.boardName.empty() || record.runtimeId.empty() ||
|
|
954
|
+
record.coordinatorUrl.empty() || record.ticket.empty())
|
|
955
|
+
{
|
|
956
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
957
|
+
}
|
|
958
|
+
// The values for the references that runtime's services carry. Handed to the
|
|
959
|
+
// runtime when the coordinator builds it, and not sent to the coordinator.
|
|
960
|
+
const auto reason = coordinatorLinks->introduce(
|
|
961
|
+
record, readSecretsPayload(body.contains("secrets") ? body["secrets"] : json()));
|
|
962
|
+
if (!reason.empty())
|
|
963
|
+
{
|
|
964
|
+
auto refused = makeJsonResponse(json{{"error", reason}});
|
|
965
|
+
refused.code = 502;
|
|
966
|
+
return refused;
|
|
967
|
+
}
|
|
968
|
+
auto connected = makeJsonResponse(json{{"connected", true}});
|
|
969
|
+
connected.code = 201;
|
|
970
|
+
return connected;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
crow::response Server::impl::setRuntimeState(const crow::request &req, const std::string& runtimeId)
|
|
974
|
+
{
|
|
975
|
+
json body;
|
|
976
|
+
try
|
|
977
|
+
{
|
|
978
|
+
body = json::parse(req.body);
|
|
979
|
+
}
|
|
980
|
+
catch (const std::exception&)
|
|
981
|
+
{
|
|
982
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
983
|
+
}
|
|
984
|
+
if (!body.is_object())
|
|
985
|
+
{
|
|
986
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
987
|
+
}
|
|
988
|
+
auto settings = app->setRuntimeState(runtimeId, body);
|
|
989
|
+
if (settings.is_null())
|
|
990
|
+
{
|
|
991
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
992
|
+
}
|
|
993
|
+
return makeJsonResponse(settings);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
crow::response Server::impl::setSecrets(const crow::request &req, const std::string& runtimeId)
|
|
997
|
+
{
|
|
998
|
+
json body;
|
|
999
|
+
try
|
|
1000
|
+
{
|
|
1001
|
+
body = json::parse(req.body);
|
|
1002
|
+
}
|
|
1003
|
+
catch (const std::exception&)
|
|
1004
|
+
{
|
|
1005
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
1006
|
+
}
|
|
1007
|
+
if (!body.is_object())
|
|
1008
|
+
{
|
|
1009
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
auto held = app->setRuntimeSecrets(runtimeId, readSecretsPayload(body));
|
|
1013
|
+
if (held.is_null())
|
|
1014
|
+
{
|
|
1015
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
1016
|
+
}
|
|
1017
|
+
return makeJsonResponse(held);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
crow::response Server::impl::setAssets(const crow::request &req, const std::string& runtimeId)
|
|
1021
|
+
{
|
|
1022
|
+
json body;
|
|
1023
|
+
try
|
|
1024
|
+
{
|
|
1025
|
+
body = json::parse(req.body);
|
|
1026
|
+
}
|
|
1027
|
+
catch (const std::exception&)
|
|
1028
|
+
{
|
|
1029
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
1030
|
+
}
|
|
1031
|
+
if (!body.is_object() && !body.is_array())
|
|
1032
|
+
{
|
|
1033
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
auto held = app->setRuntimeAssets(runtimeId, readAssetsPayload(body));
|
|
1037
|
+
if (held.is_null())
|
|
1038
|
+
{
|
|
1039
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
1040
|
+
}
|
|
1041
|
+
return makeJsonResponse(held);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
crow::response Server::impl::rearrangeServices(const crow::request &req, const std::string& runtimeId)
|
|
1045
|
+
{
|
|
1046
|
+
auto rt = app->getRuntime(runtimeId);
|
|
1047
|
+
if (!rt)
|
|
1048
|
+
{
|
|
1049
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
auto body = json::parse(req.body);
|
|
1053
|
+
if (!body.is_array())
|
|
1054
|
+
{
|
|
1055
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
auto serviceOrdering = validateServiceOrdering(body);
|
|
1059
|
+
if (!serviceOrdering)
|
|
1060
|
+
{
|
|
1061
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
auto res = app->rearrangeServices(runtimeId, *serviceOrdering);
|
|
1065
|
+
if (res.is_null())
|
|
1066
|
+
{
|
|
1067
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
return makeJsonResponse(res);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// Builds the Data variant a request body means, by its content type. Shared
|
|
1074
|
+
// by the runtime-wide process route and the per-service one, so an upload
|
|
1075
|
+
// reaches a service the same way whichever entry point it arrives through.
|
|
1076
|
+
static std::optional<Data> buildInputData(const crow::request& req,
|
|
1077
|
+
const std::string& contentType)
|
|
1078
|
+
{
|
|
1079
|
+
|
|
1080
|
+
if (contentType == "application/json")
|
|
1081
|
+
{
|
|
1082
|
+
try
|
|
1083
|
+
{
|
|
1084
|
+
auto body = json::parse(req.body);
|
|
1085
|
+
// `null` is a payload: it says run with nothing on the input, which is
|
|
1086
|
+
// how a caller writes "no input" in a format that has no undefined.
|
|
1087
|
+
// Undefined is what the first service is handed, the same as over the
|
|
1088
|
+
// socket. A body that is absent entirely is still refused, above.
|
|
1089
|
+
if (body.is_null())
|
|
1090
|
+
return Data();
|
|
1091
|
+
if (!body.is_object())
|
|
1092
|
+
return std::nullopt;
|
|
1093
|
+
return Data(std::move(body));
|
|
1094
|
+
}
|
|
1095
|
+
catch (...) { return std::nullopt; }
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
if (contentType.rfind("text/plain", 0) == 0)
|
|
1099
|
+
return Data(req.body);
|
|
1100
|
+
|
|
1101
|
+
if (contentType.rfind("multipart/form-data", 0) == 0)
|
|
1102
|
+
{
|
|
1103
|
+
auto msg = crow::multipart::message(req);
|
|
1104
|
+
if (msg.parts.empty())
|
|
1105
|
+
return std::nullopt;
|
|
1106
|
+
|
|
1107
|
+
// Use the first part as the binary payload; collect its headers as meta.
|
|
1108
|
+
auto& firstPart = msg.parts[0];
|
|
1109
|
+
BinaryData binary(firstPart.body.begin(), firstPart.body.end());
|
|
1110
|
+
|
|
1111
|
+
json meta = json::object();
|
|
1112
|
+
for (auto& [headerName, header] : firstPart.headers)
|
|
1113
|
+
{
|
|
1114
|
+
json headerMeta;
|
|
1115
|
+
headerMeta["value"] = header.value;
|
|
1116
|
+
for (auto& [paramName, paramValue] : header.params)
|
|
1117
|
+
headerMeta["params"][paramName] = paramValue;
|
|
1118
|
+
meta[headerName] = std::move(headerMeta);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
return Data(MixedData{std::move(meta), std::move(binary)});
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Raw binary fallback: image/*, application/octet-stream, etc.
|
|
1125
|
+
// Promote to MixedData when a filename is present in Content-Disposition so
|
|
1126
|
+
// that downstream services (e.g. filesystem) can use the original filename.
|
|
1127
|
+
{
|
|
1128
|
+
BinaryData binary(req.body.begin(), req.body.end());
|
|
1129
|
+
auto contentDisposition = req.get_header_value("Content-Disposition");
|
|
1130
|
+
std::string filename;
|
|
1131
|
+
if (!contentDisposition.empty())
|
|
1132
|
+
{
|
|
1133
|
+
auto pos = contentDisposition.find("filename=");
|
|
1134
|
+
if (pos != std::string::npos)
|
|
1135
|
+
{
|
|
1136
|
+
filename = contentDisposition.substr(pos + 9);
|
|
1137
|
+
// Strip surrounding quotes if present
|
|
1138
|
+
if (filename.size() >= 2 && filename.front() == '"')
|
|
1139
|
+
filename = filename.substr(1, filename.size() - 2);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (!filename.empty())
|
|
1143
|
+
{
|
|
1144
|
+
json meta = json::object();
|
|
1145
|
+
meta["path"] = filename;
|
|
1146
|
+
auto uploadId = req.get_header_value("X-Upload-Id");
|
|
1147
|
+
auto chunkIndexStr = req.get_header_value("X-Chunk-Index");
|
|
1148
|
+
auto totalChunksStr= req.get_header_value("X-Total-Chunks");
|
|
1149
|
+
if (!uploadId.empty()) meta["uploadId"] = uploadId;
|
|
1150
|
+
if (!chunkIndexStr.empty())
|
|
1151
|
+
{
|
|
1152
|
+
try { meta["chunkIndex"] = std::stoi(chunkIndexStr); } catch (...) {}
|
|
1153
|
+
}
|
|
1154
|
+
if (!totalChunksStr.empty())
|
|
1155
|
+
{
|
|
1156
|
+
try { meta["totalChunks"] = std::stoi(totalChunksStr); } catch (...) {}
|
|
1157
|
+
}
|
|
1158
|
+
return Data(MixedData{std::move(meta), std::move(binary)});
|
|
1159
|
+
}
|
|
1160
|
+
return Data(std::move(binary));
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
Caller Server::impl::callerOfRequest(const crow::request& req)
|
|
1165
|
+
{
|
|
1166
|
+
// A request a host hands over in-process (Server::handleRequest) never
|
|
1167
|
+
// passed the middleware, so there is no context to read — and nobody to
|
|
1168
|
+
// name: it comes from the host's own page, not from a token.
|
|
1169
|
+
if (!req.middleware_context)
|
|
1170
|
+
{
|
|
1171
|
+
return {};
|
|
1172
|
+
}
|
|
1173
|
+
return callerOf(crow.get_context<AuthMiddleware>(req).principal);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
crow::response Server::impl::processRuntime(const crow::request &req, const std::string& runtimeId)
|
|
1177
|
+
{
|
|
1178
|
+
auto rt = app->getRuntime(runtimeId);
|
|
1179
|
+
if (!rt)
|
|
1180
|
+
{
|
|
1181
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (req.body.empty())
|
|
1185
|
+
{
|
|
1186
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
auto contentType = req.get_header_value("Content-Type");
|
|
1190
|
+
|
|
1191
|
+
auto inputData = buildInputData(req, contentType);
|
|
1192
|
+
if (!inputData)
|
|
1193
|
+
{
|
|
1194
|
+
std::cerr << "processRuntime: cannot parse body with Content-Type: " << contentType << '\n';
|
|
1195
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
try
|
|
1199
|
+
{
|
|
1200
|
+
// An external HTTP caller is not continuing a run, it is starting one —
|
|
1201
|
+
// as whoever its token says it is.
|
|
1202
|
+
auto result = app->processRuntimeAs(
|
|
1203
|
+
runtimeId, std::move(*inputData), ProcessContext::forClient(json(), callerOfRequest(req)));
|
|
1204
|
+
|
|
1205
|
+
if (auto j = getJSONFromData(result))
|
|
1206
|
+
return makeJsonResponse(*j);
|
|
1207
|
+
|
|
1208
|
+
if (auto b = getBinaryFromData(result))
|
|
1209
|
+
{
|
|
1210
|
+
crow::response res(200);
|
|
1211
|
+
res.body = std::string(b->begin(), b->end());
|
|
1212
|
+
res.set_header("Content-Type", "application/octet-stream");
|
|
1213
|
+
return res;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (auto s = getStringFromData(result))
|
|
1217
|
+
{
|
|
1218
|
+
crow::response res(200);
|
|
1219
|
+
res.body = *s;
|
|
1220
|
+
res.set_header("Content-Type", "text/plain");
|
|
1221
|
+
return res;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
return crow::response(crow::status::OK);
|
|
1225
|
+
}
|
|
1226
|
+
catch(const std::exception& e)
|
|
1227
|
+
{
|
|
1228
|
+
std::cerr << "processRuntime error: " << e.what() << '\n';
|
|
1229
|
+
return crow::response(crow::status::INTERNAL_SERVER_ERROR);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// Runs the pipeline starting at one service, with a given payload.
|
|
1234
|
+
//
|
|
1235
|
+
// Distinct from configuring it: configure says what a service *is*, this says
|
|
1236
|
+
// do your job with this. A facade button had only the former, so anything it
|
|
1237
|
+
// needed to cause had to be smuggled in as a config field a service read as a
|
|
1238
|
+
// command.
|
|
1239
|
+
crow::response Server::impl::processService(const crow::request &req, const std::string& runtimeId, const std::string& instanceId)
|
|
1240
|
+
{
|
|
1241
|
+
auto rt = app->getRuntime(runtimeId);
|
|
1242
|
+
if (!rt)
|
|
1243
|
+
{
|
|
1244
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
auto contentType = req.get_header_value("Content-Type");
|
|
1248
|
+
// An empty body is a service being told to act on nothing, which is a
|
|
1249
|
+
// reasonable thing to ask of one that takes no input.
|
|
1250
|
+
auto inputData = req.body.empty() ? std::optional<Data>(json::object())
|
|
1251
|
+
: buildInputData(req, contentType);
|
|
1252
|
+
if (!inputData)
|
|
1253
|
+
{
|
|
1254
|
+
return crow::response(crow::status::BAD_REQUEST);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
try
|
|
1258
|
+
{
|
|
1259
|
+
// The run is the one the body names, or a new one. The caller is never
|
|
1260
|
+
// the body's to name: it is whoever the token was verified as.
|
|
1261
|
+
json wire;
|
|
1262
|
+
if (const auto asJson = getJSONFromData(*inputData); asJson && asJson->is_object())
|
|
1263
|
+
{
|
|
1264
|
+
if (const auto it = asJson->find("__context"); it != asJson->end())
|
|
1265
|
+
{
|
|
1266
|
+
wire = *it;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
auto result = app->processServiceAtAs(
|
|
1270
|
+
runtimeId, instanceId, std::move(*inputData),
|
|
1271
|
+
ProcessContext::forClient(wire, callerOfRequest(req)));
|
|
1272
|
+
|
|
1273
|
+
if (auto j = getJSONFromData(result))
|
|
1274
|
+
return makeJsonResponse(*j);
|
|
1275
|
+
|
|
1276
|
+
if (auto b = getBinaryFromData(result))
|
|
1277
|
+
{
|
|
1278
|
+
crow::response res(200);
|
|
1279
|
+
res.body = std::string(b->begin(), b->end());
|
|
1280
|
+
res.set_header("Content-Type", "application/octet-stream");
|
|
1281
|
+
return res;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
if (auto s = getStringFromData(result))
|
|
1285
|
+
{
|
|
1286
|
+
crow::response res(200);
|
|
1287
|
+
res.body = *s;
|
|
1288
|
+
res.set_header("Content-Type", "text/plain");
|
|
1289
|
+
return res;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
return crow::response(crow::status::OK);
|
|
1293
|
+
}
|
|
1294
|
+
catch (const std::exception& e)
|
|
1295
|
+
{
|
|
1296
|
+
// The runtime holds no service by that id; anything else it throws is a
|
|
1297
|
+
// service failing, which is not the caller's mistake.
|
|
1298
|
+
std::cerr << "processService error: " << e.what() << '\n';
|
|
1299
|
+
return crow::response(crow::status::NOT_FOUND);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
crow::response Server::impl::getRuntimeInputs(const crow::request &req, const std::string& runtimeId)
|
|
1304
|
+
{
|
|
1305
|
+
auto rt = app->getRuntime(runtimeId);
|
|
1306
|
+
if (!rt)
|
|
1307
|
+
{
|
|
1308
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
auto arr = json::array();
|
|
1312
|
+
for (auto& input : rt->inputs)
|
|
1313
|
+
{
|
|
1314
|
+
arr.push_back(jsonSerialise(input));
|
|
1315
|
+
}
|
|
1316
|
+
return makeJsonResponse(arr);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
crow::response Server::impl::getRuntimeInput(const crow::request &req, const std::string& runtimeId, const std::string& inputId)
|
|
1320
|
+
{
|
|
1321
|
+
auto rt = app->getRuntime(runtimeId);
|
|
1322
|
+
if (!rt)
|
|
1323
|
+
{
|
|
1324
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
1325
|
+
}
|
|
1326
|
+
for (auto& input : rt->inputs)
|
|
1327
|
+
{
|
|
1328
|
+
if (input.id == inputId)
|
|
1329
|
+
{
|
|
1330
|
+
return makeJsonResponse(jsonSerialise(input));
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return crow::response{crow::status::NOT_FOUND};
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
bool Server::impl::wsOnAccept(const crow::request& req, void** userdata)
|
|
1337
|
+
{
|
|
1338
|
+
// Same policy as the REST AuthMiddleware: no-auth mode and loopback clients
|
|
1339
|
+
// are allowed unless a foreign page is asking (a browser always states the
|
|
1340
|
+
// page's origin on a handshake); otherwise the socket must present a ticket,
|
|
1341
|
+
// or a token that verifies and is allow-listed (see below).
|
|
1342
|
+
const bool local = authenticator->isNoAuth() ||
|
|
1343
|
+
isLoopbackHost(callerAddress(req, frontDoor ? frontDoor->frontSecret() : std::string()));
|
|
1344
|
+
bool allowed = local && originGate.admits(req);
|
|
1345
|
+
// Whoever a token names; nobody for a socket let in without one.
|
|
1346
|
+
Caller caller;
|
|
1347
|
+
// A browser presents a ticket it was given for its token (ws_tickets.h);
|
|
1348
|
+
// `?access_token=` is what browsers sent before tickets, and is still taken,
|
|
1349
|
+
// though it puts the token where logs keep it.
|
|
1350
|
+
if (!allowed)
|
|
1351
|
+
{
|
|
1352
|
+
if (const char* ticket = req.url_params.get("ticket"))
|
|
1353
|
+
{
|
|
1354
|
+
if (auto held = wsTickets.redeem(ticket))
|
|
1355
|
+
{
|
|
1356
|
+
allowed = true;
|
|
1357
|
+
caller = std::move(*held);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
if (!allowed)
|
|
1362
|
+
{
|
|
1363
|
+
if (const char* token = req.url_params.get("access_token"))
|
|
1364
|
+
{
|
|
1365
|
+
const auto result = authenticator->authorize(std::string("Bearer ") + token);
|
|
1366
|
+
allowed = result.status == AuthStatus::Ok;
|
|
1367
|
+
if (allowed)
|
|
1368
|
+
{
|
|
1369
|
+
caller = callerOf(result.principal);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
if (!allowed)
|
|
1374
|
+
{
|
|
1375
|
+
return false; // reject the upgrade
|
|
1376
|
+
}
|
|
1377
|
+
auto* state = new WsConnState();
|
|
1378
|
+
state->caller = caller;
|
|
1379
|
+
*userdata = state;
|
|
1380
|
+
return true;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
void Server::impl::wsOnMessage(crow::websocket::connection& conn, const std::string& message, bool isBinary)
|
|
1384
|
+
{
|
|
1385
|
+
auto* state = static_cast<WsConnState*>(conn.userdata());
|
|
1386
|
+
if (!state)
|
|
1387
|
+
{
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// The first frame is the protocol handshake ({type, id}); it binds this
|
|
1392
|
+
// connection to a runtime. Everything after it is runtime traffic.
|
|
1393
|
+
if (state->runtimeId.empty())
|
|
1394
|
+
{
|
|
1395
|
+
try
|
|
1396
|
+
{
|
|
1397
|
+
auto protocol = WebsocketProtocol::parse(message);
|
|
1398
|
+
state->runtimeId = protocol.id;
|
|
1399
|
+
state->type = protocol.type;
|
|
1400
|
+
std::lock_guard<std::mutex> lock(wsMutex);
|
|
1401
|
+
wsByRuntime[state->runtimeId].insert(&conn);
|
|
1402
|
+
}
|
|
1403
|
+
catch (const std::exception& e)
|
|
1404
|
+
{
|
|
1405
|
+
std::cerr << "Server WS: invalid protocol handshake: " << e.what() << std::endl;
|
|
1406
|
+
}
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
app->dispatchRuntimeWsMessage(state->runtimeId, message, isBinary, &state->caller);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
void Server::impl::wsOnClose(crow::websocket::connection& conn)
|
|
1414
|
+
{
|
|
1415
|
+
auto* state = static_cast<WsConnState*>(conn.userdata());
|
|
1416
|
+
if (!state)
|
|
1417
|
+
{
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
bool abandoned = false;
|
|
1421
|
+
{
|
|
1422
|
+
std::lock_guard<std::mutex> lock(wsMutex);
|
|
1423
|
+
auto it = wsByRuntime.find(state->runtimeId);
|
|
1424
|
+
if (it != wsByRuntime.end())
|
|
1425
|
+
{
|
|
1426
|
+
it->second.erase(&conn);
|
|
1427
|
+
if (it->second.empty())
|
|
1428
|
+
{
|
|
1429
|
+
wsByRuntime.erase(it);
|
|
1430
|
+
abandoned = true;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
if (abandoned)
|
|
1436
|
+
{
|
|
1437
|
+
reapIfAbandoned(state->runtimeId);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
delete state;
|
|
1441
|
+
conn.userdata(nullptr);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* Tears down a runtime whose creator asked for cleanup, now that its last
|
|
1446
|
+
* client has disconnected.
|
|
1447
|
+
*
|
|
1448
|
+
* Whoever created it said whether it should outlive its clients. A browser
|
|
1449
|
+
* running a board is that board's controller and asks for cleanup: closing the
|
|
1450
|
+
* tab should not leave runtimes behind. A coordinator, a config file or a
|
|
1451
|
+
* script says nothing, and their runtimes stay until deleted — a headless
|
|
1452
|
+
* runtime is not an abandoned one, and a runtime is never reaped because of who
|
|
1453
|
+
* happened to connect to it.
|
|
1454
|
+
*/
|
|
1455
|
+
void Server::impl::reapIfAbandoned(const std::string& runtimeId)
|
|
1456
|
+
{
|
|
1457
|
+
// getConfiguration carries the declaration back, so the public API is enough
|
|
1458
|
+
// to answer this — no need to reach for the Runtime itself.
|
|
1459
|
+
auto config = app->getRuntime(runtimeId);
|
|
1460
|
+
if (!config || !config->garbageCollected)
|
|
1461
|
+
{
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
std::cout << "Server: releasing runtime \"" << runtimeId
|
|
1465
|
+
<< "\" — its last client disconnected and it asked to be cleaned up"
|
|
1466
|
+
<< std::endl;
|
|
1467
|
+
app->removeRuntime(runtimeId);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
void Server::impl::sendText(const std::string& runtimeId, const std::string& text)
|
|
1471
|
+
{
|
|
1472
|
+
// A text frame rather than the binary one notifications take: what travels
|
|
1473
|
+
// here is a JSON message whose `type` the receiver dispatches on, not a YAS
|
|
1474
|
+
// frame carrying a Data.
|
|
1475
|
+
std::lock_guard<std::mutex> lock(wsMutex);
|
|
1476
|
+
auto it = wsByRuntime.find(runtimeId);
|
|
1477
|
+
if (it == wsByRuntime.end())
|
|
1478
|
+
{
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
for (auto* conn : it->second)
|
|
1482
|
+
{
|
|
1483
|
+
auto* state = static_cast<WsConnState*>(conn->userdata());
|
|
1484
|
+
if (state && state->type == "writer")
|
|
1485
|
+
{
|
|
1486
|
+
continue; // write-only clients don't receive anything back
|
|
1487
|
+
}
|
|
1488
|
+
conn->send_text(text);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
void Server::impl::sendNotification(const std::string& runtimeId, const std::string& frame)
|
|
1493
|
+
{
|
|
1494
|
+
std::lock_guard<std::mutex> lock(wsMutex);
|
|
1495
|
+
auto it = wsByRuntime.find(runtimeId);
|
|
1496
|
+
if (it == wsByRuntime.end())
|
|
1497
|
+
{
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
for (auto* conn : it->second)
|
|
1501
|
+
{
|
|
1502
|
+
auto* state = static_cast<WsConnState*>(conn->userdata());
|
|
1503
|
+
if (state && state->type == "writer")
|
|
1504
|
+
{
|
|
1505
|
+
continue; // write-only clients don't receive notifications
|
|
1506
|
+
}
|
|
1507
|
+
conn->send_binary(frame);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
void Server::sendNotification(const std::string& runtimeId, const std::string& frame)
|
|
1512
|
+
{
|
|
1513
|
+
m_impl->sendNotification(runtimeId, frame);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
void Server::sendText(const std::string& runtimeId, const std::string& text)
|
|
1517
|
+
{
|
|
1518
|
+
m_impl->sendText(runtimeId, text);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
void Server::setAllowedUsers(const std::vector<std::string>& emails)
|
|
1522
|
+
{
|
|
1523
|
+
m_impl->authenticator->setAllowedEmails(emails);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
std::string Server::mintProcessRuntimeGrant(const std::string& runtimeId, std::chrono::seconds ttl)
|
|
1527
|
+
{
|
|
1528
|
+
// Only mint for a runtime that actually exists in this process, so a token can
|
|
1529
|
+
// never be scoped to a phantom endpoint.
|
|
1530
|
+
if (runtimeId.empty() || !m_impl->app->getRuntime(runtimeId))
|
|
1531
|
+
{
|
|
1532
|
+
std::cerr << "[auth] refusing to mint process-runtime grant for unknown or empty runtime '"
|
|
1533
|
+
<< runtimeId << "'" << std::endl;
|
|
1534
|
+
return "";
|
|
1535
|
+
}
|
|
1536
|
+
return m_impl->capabilities.mintProcessRuntimeGrant(runtimeId, ttl);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
}
|