schaefersoft/laravel-swiss-eid
最新稳定版本:v0.4.0
Composer 安装命令:
composer require schaefersoft/laravel-swiss-eid
包简介
Laravel package for integrating the Swiss eID (swiyu) verification flow
README 文档
README
Laravel package for integrating the Swiss eID (swiyu) verification flow: builds the OpenID4VP / DIF Presentation Exchange request, talks to a swiyu Verifier, persists the verification, handles the webhook callback and emits events. UI is intentionally left to you — the package only exposes data primitives (QR code SVG, deeplink, JSON status endpoint) so you can render in Blade, Livewire, Vue, React, or anything else.
5-Minute Quickstart
This package is a client for the swiyu Verifier — the Spring Boot service that speaks OpenID4VP to the wallet app. You need that verifier running before anything else works.
Step 1 — Start the verifier locally
git clone https://github.com/swiyu-admin-ch/swiyu-verifier cd swiyu-verifier docker compose up -d # Listening on http://localhost:8083
The wallet app on the user's phone must be able to reach the verifier, so expose it through a public tunnel during local development:
ngrok http 8083
# → https://abc123.ngrok-free.app (use this URL in .env below)
Step 2 — Install the package
composer require schaefersoft/laravel-swiss-eid php artisan swiss-eid:install
The installer publishes the config file and migration, prints all required .env
variables, and optionally runs php artisan migrate.
Step 3 — Fill in .env
SWISS_EID_VERIFIER_URL=https://abc123.ngrok-free.app SWISS_EID_WEBHOOK_API_KEY=a-secret-key-at-least-32-characters-long # From your swiyu verifier configuration: SWISS_EID_CREDENTIAL_TYPE=https://eid.admin.ch/credentials/swiss-eid-beta/1.0 SWISS_EID_ACCEPTED_ISSUERS=did:tdw:QmPEZPhDFR4nEYSFK5bMnvECqdpf1tPTPJuWs9QrMjCumw:identifier-reg.trust-infra.swiyu-int.admin.ch:api:v1:did:9a5559f0-b81c-4368-a170-e7b4ae424527
Step 4 — Start a verification and show the QR code
use SwissEid\LaravelSwissEid\Facades\SwissEid; $pending = SwissEid::verify() ->ageOver18() ->forUser(auth()->id()) ->create(); // $pending->qrCode() → SVG string, embed with {!! !!} // $pending->deeplink → universal link to open the wallet app directly // $pending->statusUrl() → JSON polling endpoint for your frontend // $pending->id → UUID to look up the result later
{{-- resources/views/verify.blade.php --}} {!! $pending->qrCode(300) !!} <a href="{{ $pending->deeplink }}">Open in Swiss Wallet App</a>
Step 5 — React to the result
Once the wallet has scanned the QR code the verifier fires the webhook. Listen to the event:
use SwissEid\LaravelSwissEid\Events\VerificationCompleted; Event::listen(VerificationCompleted::class, function ($event) { $result = $event->verification->toResult(); if ($result->isSuccessful() && $result->isAdult()) { $user->update(['verified_at' => now()]); } });
Or poll the status endpoint from the frontend:
const poll = async (statusUrl) => { const { state, label, is_terminal } = await fetch(statusUrl).then(r => r.json()); document.querySelector('#status').textContent = label; // "Pending" / "Successful" … if (!is_terminal) setTimeout(() => poll(statusUrl), 2500); }; poll('{{ $pending->statusUrl() }}');
Verify your setup
php artisan swiss-eid:doctor
Validates all ENV variables, parses the private key, checks DID formats, and probes webhook reachability — in one pass.
Table of Contents
- Prerequisites
- Installation
- Configuration
- Usage
- Database
- Artisan Commands
- Testing
- Troubleshooting
- Contributing
- License
Prerequisites
Before installing the package you need a few pieces of Swiss eID infrastructure in place. Each sub-section below covers exactly one requirement.
1. PHP & Laravel versions
| Dependency | Version |
|---|---|
| PHP | 8.1+ (runtime); CI tests 8.2–8.5 |
| Laravel | 10, 11, 12, 13 |
| Composer | 2.x |
The package itself runs on PHP 8.1+ (uses native enums, readonly DTOs and
HasUuids). CI only tests against PHP 8.2+ because the Pest/PHPUnit dev
toolchain no longer resolves on PHP 8.1. Consumers on PHP 8.1 can still
install and use the package without issue.
2. A running swiyu Verifier
The Swiss eID ecosystem separates the relying party (your Laravel app) from the verifier service, a small Spring Boot process that speaks OpenID4VP to the wallet. This package is a client of that verifier — it does not implement the OpenID4VP protocol itself.
You need to run the official verifier locally (or host it somewhere) before this package can do anything:
- Repository: https://github.com/swiyu-admin-ch/swiyu-verifier
- Default port:
8083 - Expected API base path:
/management/api
A minimal docker-compose.yml typically looks like this (see the upstream
repo for the full example):
services: swiyu-verifier: image: swiyu-verifier:local ports: - "8083:8080" environment: SWIYU_VERIFIER_DID: "did:tdw:...your-verifier-did..." SWIYU_SIGNING_KEY: | -----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY----- LARAVEL_WEBHOOK_URL: "http://your-laravel-host/swiss-eid/webhook" LARAVEL_WEBHOOK_API_KEY: "your-secret-key-here"
Confirm the verifier is reachable:
curl http://localhost:8083/management/api/verifications/anything
# 404 is fine — the connection works and the API responds.
3. Public reachability & webhook routing
The verification flow requires two network paths that both have to work:
-
Wallet → Verifier: the swiyu wallet on the user's phone fetches the presentation request from the verifier's public URL. During local development, expose the verifier via a tunnel (e.g. ngrok, Cloudflare Tunnel):
ngrok http 8083 # → https://something.ngrok-free.devPut that tunnel URL into your Laravel
.envasSWISS_EID_VERIFIER_URL. The verifier itself also needs to know its public URL in its own configuration so it can embed it in the QR-code deeplink. -
Verifier → Laravel webhook: when the wallet has responded, the verifier POSTs to your Laravel app. If both run in Docker, the verifier must be able to resolve your Laravel host. Two common options:
- Run the verifier on the same Docker network as your Laravel app
(e.g. the DDEV project network) and use the internal hostname in
LARAVEL_WEBHOOK_URL, such ashttp://ddev-myproject-web/swiss-eid/webhook. - Or expose Laravel publicly too (another tunnel) and use that URL.
The webhook is authenticated via a shared secret — the verifier sends
X-Verifier-Api-Key: <key>and the package's middleware rejects everything else with401. - Run the verifier on the same Docker network as your Laravel app
(e.g. the DDEV project network) and use the internal hostname in
4. Verifier signing key (PEM)
The verifier signs the presentation request it hands to the wallet. It expects an EC P-256 private key in PEM format. Generate one with OpenSSL:
openssl ecparam -name prime256v1 -genkey -noout -out verifier-key.pem
# Public key (publish as part of your DID document):
openssl ec -in verifier-key.pem -pubout -out verifier-pub.pem
When passing the private key through a .env file, keep the real newlines
intact. Use a double-quoted multi-line value — not \n escape sequences,
which the verifier will refuse to parse:
SWIYU_SIGNING_KEY="-----BEGIN EC PRIVATE KEY----- MHcCAQEEIE+... -----END EC PRIVATE KEY-----"
5. Accepted issuer DIDs
The Swiss eID beta trust infrastructure uses did:tdw: identifiers. For a
verification to succeed, the credential presented by the wallet must be issued
by a DID that your verifier trusts. In practice you will list at least:
- Your own verifier DID (useful for self-issued test credentials).
- The official Beta-ID issuer DID if you want to accept real beta credentials.
Example for .env (comma-separated):
SWISS_EID_ACCEPTED_ISSUERS=did:tdw:Qm...your-verifier:...,did:tdw:QmPEZPhDFR4nEYSFK5bMnvECqdpf1tPTPJuWs9QrMjCumw:identifier-reg.trust-infra.swiyu-int.admin.ch:api:v1:did:9a5559f0-b81c-4368-a170-e7b4ae424527
If you do not know the issuer DID of a credential, you can extract it from a
decoded SD-JWT (the iss field) or from the verifier logs when it rejects a
presentation with issuer_not_accepted.
6. Swiss wallet app (for testing)
To actually scan a QR code and complete a verification end-to-end you need the swiyu wallet app installed on a phone:
- iOS: App Store — search "swiyu"
- Android: Google Play — search "swiyu"
For beta/integration testing against trust-infra.swiyu-int.admin.ch, the app
must be in the corresponding environment. Check the official documentation at
https://www.eid.admin.ch for environment-specific instructions.
Installation
composer require schaefersoft/laravel-swiss-eid
Run the installer:
php artisan swiss-eid:install
This publishes the config file, the migration, prints the required .env
variables, and (optionally) runs php artisan migrate.
Manual alternative:
php artisan vendor:publish --tag=swiss-eid-config php artisan vendor:publish --tag=swiss-eid-migrations php artisan migrate
Configuration
All settings live in config/swiss-eid.php and can be overridden with
environment variables:
| Variable | Default | Description |
|---|---|---|
SWISS_EID_VERIFIER_URL |
http://localhost:8083 |
Base URL of the swiyu verifier (must be reachable from Laravel). |
SWISS_EID_TIMEOUT |
10 |
HTTP timeout (seconds) for calls to the verifier. |
SWISS_EID_WEBHOOK_PATH |
/swiss-eid/webhook |
Route the verifier POSTs to when a wallet has responded. |
SWISS_EID_WEBHOOK_KEY_HEADER |
X-Verifier-Api-Key |
HTTP header carrying the shared webhook secret. |
SWISS_EID_WEBHOOK_API_KEY |
– | Shared secret; required — the middleware returns 401 without it. |
SWISS_EID_RESPONSE_MODE |
direct_post |
Response mode for wallet responses. Use direct_post.jwt for encrypted wallet-to-verifier transport (requires verifier v2.3.1+). |
SWISS_EID_CREDENTIAL_TYPE |
– | Credential type (vct) to request from the wallet. Required — set to the vct matching your swiyu environment. |
SWISS_EID_ACCEPTED_ISSUERS |
– | Comma-separated list of accepted issuer DIDs. At least one is required. |
SWISS_EID_VERIFICATION_TTL |
300 |
Seconds a pending verification stays valid before being marked expired. |
SWISS_EID_POLLING_ENABLED |
true |
Enable the built-in /swiss-eid/status/{id} JSON endpoint. |
SWISS_EID_POLLING_PATH |
/swiss-eid/status |
Route prefix of the polling endpoint. |
SWISS_EID_AUTH_ENABLED |
false |
Enable OAuth2 client-credentials auth against the verifier's management API. |
SWISS_EID_TOKEN_URL |
– | OAuth2 token endpoint (only if auth is enabled). |
SWISS_EID_CLIENT_ID |
– | OAuth2 client ID (only if auth is enabled). |
SWISS_EID_CLIENT_SECRET |
– | OAuth2 client secret (only if auth is enabled). |
SWISS_EID_TABLE_NAME |
eid_verifications |
Override the DB table name if it clashes with your schema. |
SWISS_EID_USER_ID_TYPE |
int |
Column type for user_id: int (unsignedBigInteger), uuid, or string. Set before running the migration. |
Usage
Starting a verification
Use the SwissEid facade's fluent builder. Each call returns the same manager
instance, so you can chain freely. create() persists the record and returns
a PendingVerification DTO.
use SwissEid\LaravelSwissEid\Facades\SwissEid; $pending = SwissEid::verify() ->ageOver18() ->forUser($user->id) ->metadata(['order_id' => $order->id]) ->create();
The returned $pending has:
| Property / method | Description |
|---|---|
$pending->id |
Internal UUID of the DB record. Use this to look it up later. |
$pending->verifierId |
ID assigned by the swiyu verifier. |
$pending->deeplink |
Universal link — open on the user's phone to launch the wallet. |
$pending->verificationUrl |
Full URL of the presentation request (for debugging). |
$pending->qrCode(int $size = 300) |
SVG string. Embed with {!! !!}. |
$pending->qrCodeDataUri(int $size = 300) |
data:image/svg+xml;base64,... for <img src>. |
$pending->statusUrl() |
URL of the JSON polling endpoint for this verification. |
$pending->expiresAt |
Carbon instance — TTL cutoff. |
$pending->isExpired() |
Quick boolean check. |
Requesting specific fields
Use the CredentialField enum (preferred) or plain field-name strings:
use SwissEid\LaravelSwissEid\Enums\CredentialField; $pending = SwissEid::verify() ->fields([ CredentialField::GivenName, CredentialField::FamilyName, CredentialField::DateOfBirth, CredentialField::Nationality, ]) ->create();
Available cases: AgeOver18, AgeOver16, GivenName, FamilyName,
DateOfBirth (resolves to the JSON key birth_date), Nationality,
PlaceOfBirth, Gender.
You can also pass a single field by name or full JSON path:
SwissEid::verify()->field('given_name'); // resolves to $.given_name SwissEid::verify()->field('$.custom_path'); // passed through verbatim
Overriding credential type / accepted issuers per request
SwissEid::verify() ->credentialType('your-credential-type') ->acceptedIssuers([ 'did:tdw:QmPEZ...your-trusted-issuer', ]) ->ageOver18() ->create();
Presenting to the user
The package is UI-agnostic. Render the primitives in any frontend:
{{-- Plain Blade, no JS framework required --}} <div> {!! $pending->qrCode(300) !!} <a href="{{ $pending->deeplink }}">Open in Swiss Wallet App</a> <small>Polling: {{ $pending->statusUrl() }}</small> </div>
// React / Vue / vanilla JS: poll the JSON endpoint const response = await fetch(statusUrl); const { state, label, is_terminal } = await response.json(); if (is_terminal && state === 'success') { /* redirect */ }
The polling endpoint returns JSON:
{
"state": "pending | success | failed | expired",
"label": "Ausstehend | Erfolgreich | Fehlgeschlagen | Abgelaufen",
"is_terminal": false
}
label is resolved via Laravel's translation system and automatically respects
App::getLocale(). See Localising status labels
below for details.
Poll every 2–3 seconds until is_terminal === true. If the TTL has passed,
the endpoint will also flip pending → expired on the first call after
expiry and dispatch the VerificationExpired event.
Localising status labels
The polling endpoint's label field and VerificationState::label() are driven
by Laravel's translation system under the swiss-eid::states namespace. The
package ships translations for German (de), French (fr), Italian (it), and
English (en).
The active locale (App::getLocale()) is used automatically — no configuration
needed. If the locale falls through to a string that does not exist in the
package's files (e.g. es), Laravel's fallback locale applies next, and then
the translation key itself is returned as-is.
Customising or adding languages
Publish the translation files once:
php artisan vendor:publish --tag=swiss-eid-lang
This copies the four files to lang/vendor/swiss-eid/{de,en,fr,it}/states.php.
Published files take priority over the package originals. Edit them freely:
// lang/vendor/swiss-eid/de/states.php return [ 'pending' => 'Wartet auf Bestätigung', // custom wording 'success' => 'Erfolgreich', 'failed' => 'Fehlgeschlagen', 'expired' => 'Abgelaufen', ];
To support an additional language, add a new locale directory alongside the
existing ones — e.g. lang/vendor/swiss-eid/es/states.php with Spanish labels.
Using label() directly
VerificationState::label() resolves the same translation keys, so it works
anywhere — not just in the polling JSON:
use SwissEid\LaravelSwissEid\Enums\VerificationState; $state = VerificationState::Pending; echo $state->label(); // "Ausstehend" (de), "Pending" (en), …
Handling the webhook
The webhook route (POST /swiss-eid/webhook by default) is registered
automatically by the package's service provider. It:
- Validates the
X-Verifier-Api-Keyheader via middleware. - Reads the
verification_idfrom the payload. - Calls the verifier's GET endpoint to fetch the full result.
- Writes
state,credential_data(encrypted at rest) andwebhook_received_atto the DB. - Dispatches
VerificationCompletedorVerificationFailed.
You do not register this route yourself. Just make sure:
SWISS_EID_WEBHOOK_API_KEYmatches what the verifier sends.- Your firewall / reverse proxy allows the verifier to reach that path.
- CSRF is not a concern — the route lives in the
apimiddleware group.
Retrieving results
$result = SwissEid::getVerification($pending->id); // or the verifier ID if ($result->isSuccessful()) { $firstName = $result->get('given_name'); $isAdult = $result->isAdult(); // age_over_18 convenience $raw = $result->credentialData; // decrypted array, or null }
VerificationResult methods:
| Method | Returns |
|---|---|
isSuccessful() / isFailed() / isPending() |
bool |
get(string $field, mixed $default = null) |
Field from credential data. |
has(string $field) |
bool |
isAdult() |
Shortcut for age_over_18 === true. |
toArray() |
Plain array (for JSON responses). |
If the ID is not found a VerificationNotFoundException is thrown.
Events
Listen in your EventServiceProvider or with #[AsEventListener]:
use SwissEid\LaravelSwissEid\Events\VerificationCompleted; use SwissEid\LaravelSwissEid\Events\VerificationFailed; use SwissEid\LaravelSwissEid\Events\VerificationExpired; Event::listen(VerificationCompleted::class, function ($event) { $user = User::find($event->verification->user_id); $user->update(['verified_at' => now()]); });
Each event carries a single $verification property of type
EidVerification (the Eloquent model). Use its toResult() method if you
want the DTO view.
Database
The package ships a single table (default name eid_verifications) with:
| Column | Type | Notes |
|---|---|---|
id |
UUID (PK) | Your internal ID — the one you hand to the frontend. |
verifier_id |
string | The ID returned by the swiyu verifier. |
user_id |
nullable | Your user reference. Column type depends on SWISS_EID_USER_ID_TYPE (int default, uuid, or string). |
state |
enum | pending, success, failed, expired. |
credential_type |
string | Mirrors SWISS_EID_CREDENTIAL_TYPE. |
requested_fields |
json | The presentation-definition fields you requested. |
credential_data |
encrypted json | Decrypted automatically by the cast. |
metadata |
json, nullable | Anything you passed via ->metadata([...]). |
deeplink, verification_url |
string | Cached from the verifier response. |
webhook_received_at |
datetime, nullable | Set when the webhook fires. |
expires_at |
datetime | TTL cutoff. |
created_at, updated_at |
datetime | Standard. |
Useful Eloquent scopes on EidVerification:
EidVerification::pending()->get(); EidVerification::expired()->get(); EidVerification::forUser($user->id)->latest()->first();
Override the table name with SWISS_EID_TABLE_NAME in .env. The migration
and the model both read from config('swiss-eid.table_name'), so renaming is a
single-line change.
Artisan Commands
| Command | Description |
|---|---|
swiss-eid:install |
Publish config + migration, print required .env variables, optionally run migrations. |
swiss-eid:test-connection |
Probe the verifier to confirm it is reachable and responding. |
swiss-eid:cleanup --days=7 |
Delete expired records older than N days. Accepts --dry-run. |
swiss-eid:doctor |
Validate the full configuration, parse the private key, check DID formats, and probe the webhook URL. |
Schedule the cleanup in App\Console\Kernel (or routes/console.php on
Laravel 11+):
$schedule->command('swiss-eid:cleanup --days=30')->daily();
swiss-eid:doctor
php artisan swiss-eid:doctor
A self-contained diagnostic that works through every aspect of the configuration in one pass and exits with a non-zero code if any check fails — useful in CI pipelines or as a post-deploy smoke test.
Checks performed
| Section | What is validated |
|---|---|
| Verifier | SWISS_EID_VERIFIER_URL is a valid URL · timeout is a positive integer · SWISS_EID_RESPONSE_MODE is direct_post or direct_post.jwt |
| Webhook | Path starts with / · header name is set · SWISS_EID_WEBHOOK_API_KEY is set and ≥ 32 characters (warns if shorter) |
| Credentials | SWISS_EID_CREDENTIAL_TYPE (vct) is set · at least one accepted issuer is configured |
| OAuth2 Auth | Skipped when SWISS_EID_AUTH_ENABLED=false; otherwise validates token URL, client ID and secret |
| General | SWISS_EID_VERIFICATION_TTL is a positive integer (warns if < 60 s) · SWISS_EID_USER_ID_TYPE is one of int, uuid, string |
| Private Key | Reads SWISS_EID_PRIVATE_KEY, parses it with OpenSSL, confirms the key type is EC and the curve is P-256 (prime256v1); reports key type and bit count. Required when response_mode=direct_post.jwt. |
| DID Formats | Each entry in SWISS_EID_ACCEPTED_ISSUERS matches did:[method]:[id] |
| Webhook Reachability | POSTs to APP_URL + webhook.path without credentials: 401/403 → correctly protected; 404 → route not found; connection error → not publicly reachable |
Private key check
The doctor command reads the private key directly from the environment (not from
the config file) so that the raw value can be passed to OpenSSL. Store it in
.env using double-quoted multi-line syntax to preserve real newlines:
SWISS_EID_PRIVATE_KEY="-----BEGIN EC PRIVATE KEY----- MHQCAQEEIBFv... -----END EC PRIVATE KEY-----"
Single-line values with escaped \n sequences also work — the command
normalises them before parsing.
Exit codes
| Code | Meaning |
|---|---|
0 |
All checks passed (warnings are informational only). |
1 |
One or more errors found. |
Example output
Swiss eID Doctor — configuration diagnostics
Verifier
✓ Verifier URL: https://verifier.example.com
✓ Timeout: 10s
✓ Response mode: direct_post.jwt
Webhook
✓ Webhook path: /swiss-eid/webhook
✓ API key header: X-Verifier-Api-Key
! SWISS_EID_WEBHOOK_API_KEY is shorter than 32 characters — consider a stronger secret
...
Private Key (JWT response mode)
✓ EC private key valid — curve: P-256 (prime256v1), bits: 256
DID Formats (accepted_issuers)
✓ Valid DID (method: did:tdw:…) — did:tdw:QmPEZ…
Webhook Reachability
Probing: https://your-app.example.com/swiss-eid/webhook
✓ Webhook reachable — HTTP 401 (auth middleware is rejecting unauthenticated requests correctly)
1 warning(s) — review before going to production.
Testing
Use the built-in SwissEidFake to avoid real HTTP calls in your tests:
use SwissEid\LaravelSwissEid\Facades\SwissEid; use SwissEid\LaravelSwissEid\SwissEidFake; it('starts a verification', function () { $fake = SwissEid::fake(); // ... code that calls SwissEid::verify()->ageOver18()->create() $fake->assertVerificationStarted(); }); it('reacts to a completed verification', function () { $result = SwissEidFake::fakeVerification(state: 'success', data: [ 'given_name' => 'Anna', 'age_over_18' => true, ]); $fake = SwissEid::fake([$result->id => $result]); SwissEid::getVerification($result->id); $fake->assertVerificationCompleted(fn ($r) => $r->get('given_name') === 'Anna'); });
Run the package's own test suite:
composer test composer test:coverage # Pest + min. 80% coverage composer analyse # PHPStan level 8
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
createVerificationManagementDto: Either acceptedIssuerDids or trustAnchors must be set |
SWISS_EID_ACCEPTED_ISSUERS is empty. |
Set at least one DID. |
Empty deeplink / no QR code |
The verifier's response used different key casing (e.g. verification_deeplink). |
The package already falls back through several aliases; check storage/logs/laravel.log for the raw response if a new one appears. |
Verifier returns 500 on /oid4vp/api/request-object/... |
PEM signing key is malformed (literal \n instead of real newlines). |
Use double-quoted multi-line .env value — see the signing key section. |
Webhook returns 500, logs say getaddrinfo for db failed |
Verifier container cannot resolve your Laravel/DB host. | Join the verifier to the same Docker network as your Laravel app; use the internal hostname in LARAVEL_WEBHOOK_URL. |
| Webhook never fires (404) | Stale verification IDs in retry queue from a previous failed run. | Create a fresh verification — the verifier drops stale webhook retries after a while. |
State is always failed with issuer_not_accepted |
The credential's issuer DID is not in SWISS_EID_ACCEPTED_ISSUERS. |
Add the real issuer DID (extract from wallet logs / decoded SD-JWT). |
| Wallet shows "Kein passender Nachweis verfügbar" | Requested field name does not match the credential schema (e.g. date_of_birth vs birth_date). |
Use the CredentialField enum — the package maps the correct keys. |
Quick sanity checks:
php artisan swiss-eid:doctor # full config + reachability diagnostics php artisan swiss-eid:test-connection # targeted verifier connectivity probe
Contributing
- Fork the repository.
- Create a feature branch:
git checkout -b feature/my-feature. - Run
composer testandcomposer analyse— both must pass. - Open a Pull Request. Conventional-commit messages are appreciated; they drive the automated release workflow.
License
MIT. See LICENSE for details.
统计信息
- 总下载量: 4
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 6
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-04-17