Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d42aa4e455 | ||
|
|
220559d257 |
27 changed files with 2458 additions and 298 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
node_modules/
|
||||
build/
|
||||
.env
|
||||
.env.*
|
||||
.zapierapprc
|
||||
*.log
|
||||
.DS_Store
|
||||
11
NATS.md
11
NATS.md
|
|
@ -5,6 +5,13 @@ This Zapier Platform app **does not use NATS**.
|
|||
| Direction | Address | Peer | Body |
|
||||
|-----------|---------|------|------|
|
||||
| IN | Zapier runtime | user Zap | create/search/trigger fields |
|
||||
| OUT HTTPS | middleware `/zapier/v1/timestamp`, `/wait`, `/verify`, `/status/{id}`, `/hashes/{sha256}`, `/hashes/{sha256}?includeTree=true`, REST Hook subscribe | verae-middleware | Bearer `zmw_` |
|
||||
| OUT HTTPS | middleware `/zapier/v1/auth/me` | verae-middleware | Bearer `zmw_` |
|
||||
| OUT HTTPS | `/zapier/v1/timestamp`, `/timestamp/wait`, `/timestamp/batch` | verae-middleware | Bearer `zmw_` |
|
||||
| OUT HTTPS | `/zapier/v1/verify`, `/verify/batch` | verae-middleware | Bearer `zmw_` |
|
||||
| OUT HTTPS | `/zapier/v1/status/{id}`, `/status/{id}/verification`, `/status/batch` | verae-middleware | Bearer `zmw_` |
|
||||
| OUT HTTPS | `/zapier/v1/hashes/{sha256}`, `/hashes/{sha256}?includeAttached=true&includeTree=true` | verae-middleware | Bearer `zmw_` |
|
||||
| OUT HTTPS | `/zapier/v1/receipts/{id}` | verae-middleware | Bearer `zmw_` |
|
||||
| OUT HTTPS | `/zapier/v1/webhooks/subscribe`, `/webhooks/unsubscribe` | verae-middleware | Bearer `zmw_` |
|
||||
| IN HTTPS | Zapier hook URL | webhook-deliver / in-process poller | `{ event, jobId, tenantId, status }` |
|
||||
|
||||
Job completion is a REST Hook HTTP POST from webhook-deliver, not a NATS client in Zapier.
|
||||
Job completion is a REST Hook HTTP POST from the middleware's webhook path, not a NATS client in Zapier.
|
||||
|
|
|
|||
197
README.md
197
README.md
|
|
@ -1,34 +1,109 @@
|
|||
# verae-zapier
|
||||
|
||||
Zapier Platform CLI app for Verae. Runs on **Zapier’s servers**. Calls only middleware HTTPS (`MIDDLEWARE_BASE_URL`), never NATS and never `api.veraetime.net`.
|
||||
Zapier Platform CLI app for Verae. Runs on **Zapier's servers**. Calls only middleware HTTPS (`MIDDLEWARE_BASE_URL`, `/zapier/v1/*`), never NATS and never `api.veraetime.net`.
|
||||
|
||||
**Forgejo:** https://git.georgelambert.org/marchon/verae-zapier-app
|
||||
**Middleware:** https://git.georgelambert.org/marchon/verae-middleware
|
||||
**Catalog:** https://zapier.georgelambert.org/packages/verae-zapier/README.pdf
|
||||
**Sphinx API sheets:** https://zapier.georgelambert.org/sphinx/index.html
|
||||
|
||||
## Role
|
||||
|
||||
Runs on **Zapier’s servers**. Calls only the middleware HTTPS API (`MIDDLEWARE_BASE_URL`), never NATS and never `api.veraetime.net` directly.
|
||||
Runs on **Zapier's servers**. Calls only the middleware HTTPS API (`MIDDLEWARE_BASE_URL`), never
|
||||
NATS and never `api.veraetime.net` directly. The middleware owns customer API keys (`zmw_…`),
|
||||
plans and quotas, job watching, REST Hook delivery, and the upstream Verae chain client
|
||||
(`POST /auth/login`, `/api/timestamp`, `/api/status`, `/api/verify`, … on api.veraetime.net).
|
||||
|
||||
## Planned modules
|
||||
## Modules
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `authentication.js` | Custom API key auth → `GET /zapier/v1/auth/me` |
|
||||
| `index.js` | App definition, beforeRequest, afterResponse error mapping |
|
||||
| `creates/timestamp_and_wait.js` | Primary action |
|
||||
| `creates/create_timestamp.js` | Async jobId action |
|
||||
| `creates/verify_timestamp.js` | Verify certificate |
|
||||
| `creates/batch_timestamp.js` | Batch create |
|
||||
| `searches/job_status.js` | Lookup by jobId |
|
||||
| `triggers/timestamp_completed.js` | REST Hook |
|
||||
| File | Zapier type | Middleware call |
|
||||
|------|-------------|-----------------|
|
||||
| `authentication.js` | custom API key | `GET /zapier/v1/auth/me` → connection label `tenantId (plan)` |
|
||||
| `index.js` | app definition | `beforeRequest` sends the key as `Authorization: Bearer` **and** `x-api-key`; `afterResponse` maps `{ error, code, details }` (402/403 billing → upgrade URL, 429 → ThrottledError) |
|
||||
| `lib/middleware.js` | helpers | base URL, error parsing, 404-as-no-match search helper, shared samples |
|
||||
| `lib/hookTrigger.js` | helpers | REST Hook factory (`/webhooks/subscribe`, `/webhooks/unsubscribe`) |
|
||||
| `creates/timestamp_and_wait.js` | action | `POST /timestamp/wait` (primary) |
|
||||
| `creates/create_timestamp.js` | action | `POST /timestamp` → 202 `{ jobId }` |
|
||||
| `creates/batch_timestamp.js` | action | `POST /timestamp/batch` (paid plans) |
|
||||
| `creates/verify_timestamp.js` | action | `POST /verify` |
|
||||
| `creates/batch_verify.js` | action | `POST /verify/batch` |
|
||||
| `creates/batch_status.js` | action | `POST /status/batch` |
|
||||
| `creates/get_receipt.js` | action | `GET /receipts/{jobId}` (JSON; optional PDF via `z.stashFile`) |
|
||||
| `searches/job_status.js` | search | `GET /status/{jobId}` (404 → no match) |
|
||||
| `searches/job_verification.js` | search | `GET /status/{jobId}/verification` (404 → no match) |
|
||||
| `searches/hash_lookup.js` | search | `GET /hashes/{sha256}` (404 → no match) |
|
||||
| `searches/tree_lookup.js` | search | `GET /hashes/{sha256}?includeAttached=true&includeTree=true` |
|
||||
| `searches/usage.js` | search | `GET /auth/me` → plan + month-to-date usage vs limits (Find Usage) |
|
||||
| `triggers/timestamp_completed.js` | REST Hook | event `timestamp.completed` |
|
||||
| `triggers/timestamp_failed.js` | REST Hook | event `timestamp.failed` |
|
||||
|
||||
Hook payload delivered by the middleware: `{ event, jobId, tenantId, status }`; the trigger
|
||||
adds `id = jobId` so Zapier can dedupe.
|
||||
|
||||
Create Timestamp / Create Timestamp and Wait take **Data or SHA256** (64 hex; hash large files
|
||||
outside Zapier and send only the digest, per the user guide), plus optional public / private
|
||||
metadata dictionaries. A digest that was sealed before returns the original job with
|
||||
`existing: true` and no second chain write.
|
||||
|
||||
I/O contracts: https://zapier.georgelambert.org/docs/developer/modules/function-reference.pdf
|
||||
|
||||
### Known gaps upstream (middleware / chain), not in this app
|
||||
|
||||
- `GET /hashes/{sha256}` returns **501 NOT_IMPLEMENTED** when the middleware runs against the
|
||||
live chain (`MOCK_VERAE=false`); the Verae OpenAPI has no hash lookup. The two hash searches
|
||||
surface that message verbatim.
|
||||
- `publicMetadata` / `privateMetadata` and `sha256`-only creates are accepted by the middleware
|
||||
but only honoured by its mock chain, so this app does not expose them.
|
||||
- The middleware has no "list recent events" endpoint, so `performList` on the hook triggers
|
||||
returns nothing and Zapier uses the static `sample`.
|
||||
|
||||
## Env
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `MIDDLEWARE_BASE_URL` | `http://127.0.0.1:3100` | Origin the app calls. Set per Zapier version: `zapier env:set 1.1.0 MIDDLEWARE_BASE_URL=https://mw.zapier.georgelambert.org` |
|
||||
| `MIDDLEWARE_PATH_PREFIX` | `/zapier/v1` | Route prefix at that origin. Leave the default for the middleware or the access planes; change it only if a fronting edge mounts the same routes elsewhere. |
|
||||
|
||||
## Where to point it (lab, 2026-09-14)
|
||||
|
||||
The catalog at https://zapier.georgelambert.org/ documents the live lab on NS1:
|
||||
|
||||
| Origin | What it is | Usable by this app today? |
|
||||
|--------|------------|---------------------------|
|
||||
| `https://mw.zapier.georgelambert.org` | verae-middleware (`MOCK_VERAE=true`, NATS on) | **Yes** — full `/zapier/v1/*`; accepts `Bearer` or `x-api-key`. Use this until the Zap plane proxies every route. |
|
||||
| `https://zap.zapier.georgelambert.org` | verae-access-zapier, the intended Zapier door (STATUS.md / dev-overview) | **Not yet** — it only proxies `POST /v1/timestamp` and does not forward the API key, so `auth/me`, wait, verify, status, hooks all 404/401. |
|
||||
| `https://api.zapier.georgelambert.org` | verae-access-api (direct customer API plane) | Forwards `x-api-key` for `POST /v1/timestamp` only; not for Zapier. |
|
||||
| zappier-edge `/v1/*` | billing edge (composition target; loopback :13000, tunnel only) | Not yet — covers timestamp/hashes/receipts/usage only, no `/zapier/v1` prefix. |
|
||||
|
||||
Keys: connect with a middleware **`zmw_…`** key (portal / `POST /zapier/v1/signup` /
|
||||
admin provisioning). Never a Verae username, password, or JWT. After the zappier
|
||||
composition lands, the same field takes the portal `x-api-key`; the app already sends that
|
||||
header.
|
||||
|
||||
## Develop
|
||||
|
||||
```bash
|
||||
export MIDDLEWARE_BASE_URL=https://your-middleware.example.com
|
||||
npm install
|
||||
npm test # unit tests, no network
|
||||
npx zapier-platform-cli validate --without-style
|
||||
```
|
||||
|
||||
End-to-end against a running middleware (mock chain shown; a live-chain middleware works the same).
|
||||
Against the lab middleware add `E2E_SKIP_HOOK_DELIVERY=1` (it cannot POST back to your laptop):
|
||||
|
||||
```bash
|
||||
# in verae-middleware
|
||||
PORT=3100 MOCK_VERAE=true NATS_ENABLED=false ADMIN_SECRET=dev-admin npm start
|
||||
curl -s -X POST http://127.0.0.1:3100/zapier/v1/admin/tenants \
|
||||
-H 'x-admin-secret: dev-admin' -H 'content-type: application/json' \
|
||||
-d '{"name":"E2E","plan":"pro","veraeUsername":"e2e","veraePassword":"x"}' # → apiKey
|
||||
|
||||
# in verae-zapier-app
|
||||
MIDDLEWARE_BASE_URL=http://127.0.0.1:3100 MIDDLEWARE_API_KEY=zmw_... npm run test:e2e
|
||||
|
||||
# against the live lab (key from dev-overview/05-developer-credentials or /zapier/v1/signup)
|
||||
MIDDLEWARE_BASE_URL=https://mw.zapier.georgelambert.org MIDDLEWARE_API_KEY=zmw_... \
|
||||
E2E_SKIP_HOOK_DELIVERY=1 npm run test:e2e
|
||||
```
|
||||
|
||||
## Gate
|
||||
|
|
@ -36,3 +111,97 @@ export MIDDLEWARE_BASE_URL=https://your-middleware.example.com
|
|||
```bash
|
||||
npm run gate:11 # from monorepo root, after Phase 11 implementation
|
||||
```
|
||||
|
||||
## Status of the API integration (2026-09-15)
|
||||
|
||||
Where the Zapier ↔ Verae path stands, and what is still open. Sources: this repo,
|
||||
`verae-middleware`, and the catalog (`zapier-decisions/STATUS`, `dev-overview/03…05`).
|
||||
|
||||
### Done
|
||||
|
||||
- This app covers the whole middleware `/zapier/v1` surface that Zapier needs: async / wait /
|
||||
batch create, single and batch verify, job status / verification / batch status, hash and
|
||||
tree-node lookup, usage, retrieval receipt, and REST Hooks for `timestamp.completed` and
|
||||
`timestamp.failed`.
|
||||
- The middleware already has the live chain client (`src/clients/veraeClient.js`) for
|
||||
`POST /api/timestamp`, `/api/batch/timestamp`, `/api/verify`, `/api/batch/verify`,
|
||||
`GET /api/status/{jobId}`, `GET /api/verify/{jobId}`, `POST /api/batch/status`, and
|
||||
`POST /auth/login` on https://api.veraetime.net. No Zapier-app change is needed to go live.
|
||||
- Verified end to end through `zapier-platform-core` against a mock-chain middleware
|
||||
(`npm run test:e2e`), on both a pro tenant (batch, hook delivery) and a free tenant
|
||||
(402/403 upgrade-URL mapping).
|
||||
|
||||
### Open — blocked on people, not code
|
||||
|
||||
| Item | Owner | What is needed |
|
||||
|------|-------|----------------|
|
||||
| Zapier Platform push | operator | `zapier login`, `zapier register` (or link), `zapier env:set <version> MIDDLEWARE_BASE_URL=…`, `zapier push` as a private app, then test Create Timestamp and Wait, Create Timestamp, Find Job Status, Timestamp Completed. |
|
||||
| Live chain (`MOCK_VERAE=false`) | Verae / operator | A Verae username + password for the middleware (`VERAE_API_BASE_URL=https://api.veraetime.net`). Then prove one real seal, confirm `202 { jobId }` → status → verify, and map the live status JSON into the receipt shape this app samples (`certificate`, `timestamp`, `blockIndex`, `sha256`). |
|
||||
| Live-lab e2e run | anyone with the lab key | `MIDDLEWARE_BASE_URL=https://mw.zapier.georgelambert.org MIDDLEWARE_API_KEY=zmw_… E2E_SKIP_HOOK_DELIVERY=1 npm run test:e2e`. Exercises the NATS-enabled wait / poller path that the local run does not. |
|
||||
|
||||
### Open — code in other repos
|
||||
|
||||
| Item | Repo | Notes |
|
||||
|------|------|-------|
|
||||
| Zapier plane does not proxy the app | `verae-access-zapier` | `zap.zapier.georgelambert.org` handles only `POST /v1/timestamp` and drops the API key. STATUS says to point the app there; until it proxies all `/zapier/v1/*` with the key intact, use `mw.zapier.georgelambert.org`. |
|
||||
| Hash lookup on live chain | Verae API or middleware store | `GET /hashes/{sha256}` is mock-only; live returns `501 NOT_IMPLEMENTED`. Needs a Verae API gap fill or a middleware-side index. |
|
||||
| `sha256`-only create and metadata on live chain | `verae-middleware` | Accepted by the middleware and honoured by the mock; the live client sends only `data` + `hashAlg` (adapter rule). Decide how the live path handles digest-only registration and public/private metadata. |
|
||||
| Tree-leaf receipts on wait/status | `verae-middleware` / `verae-archive-aggregator` | The user guide describes `receipts[]`, `itemizedOnMainChain`, `merkleRoot`, `proofOk`; the live wait path does not call the aggregator yet, so those fields are absent. This app's `tree_lookup` sample already names them. |
|
||||
| Billing-edge composition | `zappier-edge` | Target is Zapier → zappier `/v1/*` (`x-api-key`, metered) → middleware. The edge is loopback-only today and covers only timestamp / hashes / receipts / usage without the `/zapier/v1` prefix. This app already sends `x-api-key` and has `MIDDLEWARE_PATH_PREFIX` for when it lands. |
|
||||
| Webhook `performList` | `verae-middleware` | No "recent events" endpoint, so the hook triggers return `[]` from `performList` and Zapier uses the static sample. A small list route would give real sample data in the editor. |
|
||||
|
||||
### Not planned in this app
|
||||
|
||||
- Verae admin HTML, user CRUD, and get-block-by-hash (`/admin/*`, `/auth/users*`).
|
||||
- Encrypted object storage and shares (`/objects*`, `/shares/*`) — mock-only today; add creates
|
||||
here once the middleware contract is final.
|
||||
- Anything that talks to NATS or api.veraetime.net directly.
|
||||
|
||||
## Proposed changes to the Verae chain API
|
||||
|
||||
Context: a developer asked how to query an original hash, get a receipt back from both the
|
||||
original submission and later lookups, see every timestamp field and its metadata, and store
|
||||
metadata (or links to it) in a blockchain timestamp. Against the live OpenAPI
|
||||
(`docs/00-sources/veraetime-openapi.yaml`) none of that is possible today: lookups are by job ID
|
||||
only, `POST /api/timestamp` returns just `{ jobId }`, and the request has no metadata field.
|
||||
The middleware mocks all of it (`MOCK_VERAE=true`) but its live client may only call paths in
|
||||
the snapshot spec.
|
||||
|
||||
### Required on api.veraetime.net
|
||||
|
||||
| Ask | Change to the Verae API | Why the chain, not the middleware |
|
||||
|-----|-------------------------|-----------------------------------|
|
||||
| Query by original hash | New `GET /api/hashes/{sha256}` → `{ exists, jobId, sha256, timestamp, blockIndex, certificate }`. The chain already records `dataHash` per transaction; it needs an index on it. | Only the chain can find hashes sealed by any submitter. A middleware index only knows what passed through it. |
|
||||
| Receipt on original submission | Make `POST /api/timestamp` idempotent on the digest: if the hash already exists return `200 { jobId, existing: true, certificate, timestamp, blockIndex }` instead of `202`. Accept a pre-hashed input `{ sha256 }` alongside `{ data }` so clients never ship the document. | Idempotency has to live where the seal lives. Accepting a digest also stops raw data landing in blocks. |
|
||||
| Receipt on lookup | Have the hash lookup and `GET /api/verify/{jobId}` return the full `StatusResponse` (certificate + block metadata). The shape exists; the lookup just needs to reuse it. | No new data, a consistent response. |
|
||||
| Metadata in the timestamp | Optional `metadata` object on `TimestampRequest` with a documented size cap, stored with the transaction and returned on status, verify, and hash lookup. Add `metadataHash` as an alternative for callers who want to commit to metadata without publishing it. | Anything the seal must prove existed at that time has to be inside what the chain hashes. Everything else stays off chain. |
|
||||
|
||||
### Strongly recommended
|
||||
|
||||
- **Certified retrieval receipt** — `GET /api/receipts/{jobId}?format=json|pdf`, where the chain
|
||||
seals a `document.retrieved` event referencing the original job and returns both. The
|
||||
middleware mocks this; a chain-signed receipt is what makes it certified.
|
||||
- **JSON error bodies everywhere** — the spec promises `{ error, code }` but the live service
|
||||
returns plain text on 401 (`Unauthorized: token is missing`). Every client special-cases it.
|
||||
- **Completion callback or job list** — an optional `callbackUrl` on create, or
|
||||
`GET /api/jobs?since=…`. Today every consumer polls `GET /api/status/{jobId}` per job.
|
||||
|
||||
### Rules to write into the spec
|
||||
|
||||
- Public metadata on chain is immutable and readable by anyone. Private metadata never goes on
|
||||
chain; only a hash commitment does.
|
||||
- Define `data` as "bytes to hash" and state whether blocks retain it. The `/admin/*` examples
|
||||
show blocks carrying a base64 `data` field that decodes to the sample input, which suggests
|
||||
they do — that matters for privacy and should be confirmed on a live seal.
|
||||
|
||||
### What can be done without touching the API
|
||||
|
||||
The middleware can index SHA-256 → job ID at submission, store public/private metadata, and
|
||||
merge both back into `/status/{jobId}` and `/hashes/{sha256}` on the live path (it already does
|
||||
so in mock). That covers the developer's flow for anything submitted through Verae's own
|
||||
middleware. It cannot deliver chain-wide lookup, chain-level idempotency, or a chain-signed
|
||||
receipt — those are the real API asks in the table above.
|
||||
|
||||
This Zapier app already sends `sha256`, `publicMetadata`, and `privateMetadata` to the
|
||||
middleware and exposes Find Timestamp by SHA256, Find Job Verification Details, and Get
|
||||
Retrieval Receipt, so no app change is needed once either side lands.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# verae-zapier-app
|
||||
|
||||
**Job:** Full Zapier CLI app: timestamp, wait, verify, batch, job status, hash lookup, REST Hook.
|
||||
**Job:** Full Zapier CLI app: timestamp (async, wait, batch), verify (single, batch), job status (single, batch, verification details), hash / tree-node lookup, usage, retrieval receipt, REST Hooks for `timestamp.completed` and `timestamp.failed`.
|
||||
|
||||
**Expects:** `zmw_` middleware key (today) or zappier key after composition.
|
||||
**Expects:** `zmw_` middleware key (today) or zappier portal key after composition; sent as `Authorization: Bearer` and `x-api-key`.
|
||||
|
||||
**Sends:** HTTPS to middleware `/zapier/v1/*`.
|
||||
**Sends:** HTTPS to middleware `/zapier/v1/*` only.
|
||||
|
||||
**Does not** speak NATS.
|
||||
**Does not** speak NATS or call `api.veraetime.net`.
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
/**
|
||||
* Custom API key auth against Verae middleware.
|
||||
* Custom API key auth against the Verae middleware.
|
||||
*
|
||||
* Customers get a `zmw_…` key from signup / provisioning. The middleware
|
||||
* resolves it to a tenant (plan, quotas) and logs in to Verae on their behalf.
|
||||
* @module authentication
|
||||
*/
|
||||
|
||||
const middlewareBase = () =>
|
||||
process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||
const { url } = require('./lib/middleware');
|
||||
|
||||
/**
|
||||
* Zapier connection test — validates API key.
|
||||
* Zapier connection test — `GET /zapier/v1/auth/me`. Its fields feed
|
||||
* `connectionLabel`.
|
||||
* @param {object} z
|
||||
* @param {object} bundle
|
||||
*/
|
||||
const testAuth = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: `${middlewareBase()}/zapier/v1/auth/me`,
|
||||
url: url('/auth/me'),
|
||||
headers: {
|
||||
Authorization: `Bearer ${bundle.authData.api_key}`,
|
||||
'x-api-key': bundle.authData.api_key,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
|
|
@ -29,8 +33,7 @@ module.exports = {
|
|||
type: 'string',
|
||||
required: true,
|
||||
label: 'API Key',
|
||||
helpText:
|
||||
'Your Verae API key (zmw_…) from signup or admin provisioning.',
|
||||
helpText: 'Your Verae API key (zmw_…) from portal signup or admin provisioning. Never a Verae username, password, or JWT.',
|
||||
},
|
||||
],
|
||||
test: testAuth,
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
/** Re-export activate-now Add Numbers so this package stays self-contained. */
|
||||
module.exports = require('../../verae-activate/creates/add_numbers');
|
||||
73
creates/batch_status.js
Normal file
73
creates/batch_status.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* `POST /zapier/v1/status/batch` — status of several jobs at once.
|
||||
* Modelled as an action because it returns one aggregate result.
|
||||
* @module creates/batch_status
|
||||
*/
|
||||
|
||||
const { url, statusSample, toStringList } = require('../lib/middleware');
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const jobIds = toStringList(bundle.inputData.jobIds);
|
||||
if (jobIds.length === 0) {
|
||||
throw new z.errors.Error('Provide at least one job ID.', 'VALIDATION_ERROR', 400);
|
||||
}
|
||||
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: url('/status/batch'),
|
||||
body: { jobIds },
|
||||
});
|
||||
const map = (response.data && response.data.results) || {};
|
||||
const results = jobIds.map((id) => ({ id, ...(map[id] || { status: 'unknown' }) }));
|
||||
const count = (s) => results.filter((r) => r.status === s).length;
|
||||
|
||||
return {
|
||||
jobIds,
|
||||
results,
|
||||
total: results.length,
|
||||
completedCount: count('completed'),
|
||||
pendingCount: count('pending'),
|
||||
failedCount: count('failed'),
|
||||
allCompleted: results.length > 0 && count('completed') === results.length,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'batch_status',
|
||||
noun: 'Job',
|
||||
display: {
|
||||
label: 'Get Batch Job Status',
|
||||
description: 'Retrieves the current status of multiple timestamp jobs in one request.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'jobIds',
|
||||
label: 'Job IDs',
|
||||
type: 'string',
|
||||
required: true,
|
||||
list: true,
|
||||
helpText: 'One job ID per entry (or one per line), e.g. the Job IDs from "Create Batch Timestamps".',
|
||||
},
|
||||
],
|
||||
perform,
|
||||
sample: {
|
||||
jobIds: [statusSample.id, '650e8400-e29b-41d4-a716-446655440001'],
|
||||
results: [statusSample, { id: '650e8400-e29b-41d4-a716-446655440001', status: 'pending' }],
|
||||
total: 2,
|
||||
completedCount: 1,
|
||||
pendingCount: 1,
|
||||
failedCount: 0,
|
||||
allCompleted: false,
|
||||
},
|
||||
outputFields: [
|
||||
{ key: 'jobIds', label: 'Job IDs' },
|
||||
{ key: 'results', label: 'Results' },
|
||||
{ key: 'total', label: 'Total', type: 'integer' },
|
||||
{ key: 'completedCount', label: 'Completed Count', type: 'integer' },
|
||||
{ key: 'pendingCount', label: 'Pending Count', type: 'integer' },
|
||||
{ key: 'failedCount', label: 'Failed Count', type: 'integer' },
|
||||
{ key: 'allCompleted', label: 'All Completed', type: 'boolean' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -1,18 +1,24 @@
|
|||
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||
/**
|
||||
* `POST /zapier/v1/timestamp/batch` — several items in one request (paid plans).
|
||||
* @module creates/batch_timestamp
|
||||
*/
|
||||
|
||||
const { url, hashAlgField, toStringList } = require('../lib/middleware');
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const items = (bundle.inputData.items || '')
|
||||
.split('\n')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.map((data) => ({ data }));
|
||||
const hashAlg = bundle.inputData.hashAlg || undefined;
|
||||
const items = toStringList(bundle.inputData.items).map((data) => ({ data, hashAlg }));
|
||||
if (items.length === 0) {
|
||||
throw new z.errors.Error('Provide at least one data item to timestamp.', 'VALIDATION_ERROR', 400);
|
||||
}
|
||||
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: `${base()}/zapier/v1/timestamp/batch`,
|
||||
url: url('/timestamp/batch'),
|
||||
body: { items },
|
||||
});
|
||||
return response.data;
|
||||
const jobIds = (response.data && response.data.jobIds) || [];
|
||||
return { ...response.data, jobIds, count: jobIds.length };
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -20,20 +26,28 @@ module.exports = {
|
|||
noun: 'Timestamp',
|
||||
display: {
|
||||
label: 'Create Batch Timestamps',
|
||||
description: 'Submit multiple data lines for timestamping (paid plans).',
|
||||
description: 'Submits multiple data items for timestamping in one request (paid plans).',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'items',
|
||||
label: 'Data Lines',
|
||||
label: 'Data Items',
|
||||
type: 'text',
|
||||
required: true,
|
||||
helpText: 'One payload per line.',
|
||||
list: true,
|
||||
helpText: 'One item per entry (or one per line). Each item becomes its own timestamp job.',
|
||||
},
|
||||
hashAlgField,
|
||||
],
|
||||
perform,
|
||||
sample: { jobIds: ['id-1', 'id-2'] },
|
||||
outputFields: [{ key: 'jobIds', label: 'Job IDs' }],
|
||||
sample: {
|
||||
jobIds: ['550e8400-e29b-41d4-a716-446655440000', '650e8400-e29b-41d4-a716-446655440001'],
|
||||
count: 2,
|
||||
},
|
||||
outputFields: [
|
||||
{ key: 'jobIds', label: 'Job IDs' },
|
||||
{ key: 'count', label: 'Job Count', type: 'integer' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
67
creates/batch_verify.js
Normal file
67
creates/batch_verify.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* `POST /zapier/v1/verify/batch` — verify several certificates at once.
|
||||
* @module creates/batch_verify
|
||||
*/
|
||||
|
||||
const { url, toStringList } = require('../lib/middleware');
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const certificates = toStringList(bundle.inputData.certificates);
|
||||
if (certificates.length === 0) {
|
||||
throw new z.errors.Error('Provide at least one certificate to verify.', 'VALIDATION_ERROR', 400);
|
||||
}
|
||||
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: url('/verify/batch'),
|
||||
body: { certificates },
|
||||
});
|
||||
const results = (response.data && response.data.results) || [];
|
||||
const validCount = results.filter((r) => r && r.valid === true).length;
|
||||
return {
|
||||
results,
|
||||
total: results.length,
|
||||
validCount,
|
||||
invalidCount: results.length - validCount,
|
||||
allValid: results.length > 0 && validCount === results.length,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'batch_verify',
|
||||
noun: 'Verification',
|
||||
display: {
|
||||
label: 'Verify Batch Timestamps',
|
||||
description: 'Verifies multiple timestamp certificates in one request.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'certificates',
|
||||
label: 'Certificates',
|
||||
type: 'text',
|
||||
required: true,
|
||||
list: true,
|
||||
helpText: 'One certificate per entry (or one per line). Results come back in the same order.',
|
||||
},
|
||||
],
|
||||
perform,
|
||||
sample: {
|
||||
results: [
|
||||
{ valid: true, timestamp: '2023-01-01T12:00:00Z', blockIndex: 42 },
|
||||
{ valid: false },
|
||||
],
|
||||
total: 2,
|
||||
validCount: 1,
|
||||
invalidCount: 1,
|
||||
allValid: false,
|
||||
},
|
||||
outputFields: [
|
||||
{ key: 'results', label: 'Results' },
|
||||
{ key: 'total', label: 'Total', type: 'integer' },
|
||||
{ key: 'validCount', label: 'Valid Count', type: 'integer' },
|
||||
{ key: 'invalidCount', label: 'Invalid Count', type: 'integer' },
|
||||
{ key: 'allValid', label: 'All Valid', type: 'boolean' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||
/**
|
||||
* `POST /zapier/v1/timestamp` — submit data or a SHA-256, get a job ID (202).
|
||||
* A digest that was sealed before returns the original job with `existing: true`.
|
||||
* @module creates/create_timestamp
|
||||
*/
|
||||
|
||||
const { url, timestampBody, timestampInputFields } = require('../lib/middleware');
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: `${base()}/zapier/v1/timestamp`,
|
||||
body: {
|
||||
data: bundle.inputData.data,
|
||||
hashAlg: bundle.inputData.hashAlg || undefined,
|
||||
},
|
||||
url: url('/timestamp'),
|
||||
body: timestampBody(z, bundle.inputData),
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
|
@ -17,21 +20,21 @@ module.exports = {
|
|||
noun: 'Timestamp',
|
||||
display: {
|
||||
label: 'Create Timestamp (Async)',
|
||||
description: 'Submits data for timestamping and returns a job ID.',
|
||||
description:
|
||||
'Registers data or a SHA-256 digest for blockchain timestamping and returns a job ID right away. Pair with "Timestamp Completed" or "Find Job Status" to get the certificate.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{ key: 'data', label: 'Data', type: 'string', required: true },
|
||||
{
|
||||
key: 'hashAlg',
|
||||
label: 'Hash Algorithm',
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'SHA256',
|
||||
},
|
||||
],
|
||||
inputFields: timestampInputFields(),
|
||||
perform,
|
||||
sample: { jobId: '550e8400-e29b-41d4-a716-446655440000' },
|
||||
outputFields: [{ key: 'jobId', label: 'Job ID' }],
|
||||
sample: {
|
||||
jobId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
sha256: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
|
||||
existing: false,
|
||||
},
|
||||
outputFields: [
|
||||
{ key: 'jobId', label: 'Job ID' },
|
||||
{ key: 'sha256', label: 'SHA256' },
|
||||
{ key: 'existing', label: 'Already Timestamped', type: 'boolean' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
76
creates/get_receipt.js
Normal file
76
creates/get_receipt.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* `GET /zapier/v1/receipts/{jobId}` — certified retrieval receipt for a
|
||||
* completed job (JSON, optionally the PDF as a Zapier file). The middleware
|
||||
* seals a `document.retrieved` event on each call, so this is an action.
|
||||
* @module creates/get_receipt
|
||||
*/
|
||||
|
||||
const { url } = require('../lib/middleware');
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const jobId = bundle.inputData.jobId;
|
||||
const receiptUrl = url(`/receipts/${encodeURIComponent(jobId)}`);
|
||||
|
||||
const response = await z.request({ url: receiptUrl, params: { format: 'json' } });
|
||||
const receipt = { id: jobId, ...(response.data || {}) };
|
||||
|
||||
if (bundle.inputData.includePdf) {
|
||||
const pdfRequest = z.request({ url: receiptUrl, params: { format: 'pdf' }, raw: true });
|
||||
receipt.pdf = await z.stashFile(
|
||||
pdfRequest,
|
||||
undefined,
|
||||
`verae-receipt-${jobId}.pdf`,
|
||||
'application/pdf',
|
||||
);
|
||||
}
|
||||
return receipt;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'get_receipt',
|
||||
noun: 'Receipt',
|
||||
display: {
|
||||
label: 'Get Retrieval Receipt',
|
||||
description:
|
||||
'Fetches the certified retrieval receipt for a completed timestamp job. Fails with NOT_COMPLETE while the job is still pending.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{ key: 'jobId', label: 'Job ID', type: 'string', required: true },
|
||||
{
|
||||
key: 'includePdf',
|
||||
label: 'Include PDF',
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: 'false',
|
||||
helpText: 'Also attach the receipt as a PDF file.',
|
||||
},
|
||||
],
|
||||
perform,
|
||||
sample: {
|
||||
id: '550e8400-e29b-41d4-a716-446655440000',
|
||||
type: 'verae.retrieval-receipt',
|
||||
format: 'json',
|
||||
jobId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
sha256: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
|
||||
timestamp: '2023-01-01T12:05:00Z',
|
||||
certificate: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||
extraSeal: {
|
||||
event: 'document.retrieved',
|
||||
retrievedAt: '2023-01-02T09:00:00Z',
|
||||
tenantId: 'tenant-example',
|
||||
},
|
||||
},
|
||||
outputFields: [
|
||||
{ key: 'jobId', label: 'Job ID' },
|
||||
{ key: 'type', label: 'Receipt Type' },
|
||||
{ key: 'sha256', label: 'SHA256' },
|
||||
{ key: 'timestamp', label: 'Timestamp', type: 'datetime' },
|
||||
{ key: 'certificate', label: 'Certificate' },
|
||||
{ key: 'extraSeal__event', label: 'Seal Event' },
|
||||
{ key: 'extraSeal__retrievedAt', label: 'Retrieved At', type: 'datetime' },
|
||||
{ key: 'extraSeal__tenantId', label: 'Tenant ID' },
|
||||
{ key: 'pdf', label: 'Receipt PDF', type: 'file' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
@ -1,15 +1,27 @@
|
|||
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||
/**
|
||||
* `POST /zapier/v1/timestamp/wait` — submit data or a SHA-256 and hold until
|
||||
* the job is terminal or the middleware's WAIT_TIMEOUT_MS elapses
|
||||
* (then `status: pending` with the job ID).
|
||||
* @module creates/timestamp_and_wait
|
||||
*/
|
||||
|
||||
const {
|
||||
url,
|
||||
statusOutputFields,
|
||||
statusSample,
|
||||
timestampBody,
|
||||
timestampInputFields,
|
||||
} = require('../lib/middleware');
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: `${base()}/zapier/v1/timestamp/wait`,
|
||||
body: {
|
||||
data: bundle.inputData.data,
|
||||
hashAlg: bundle.inputData.hashAlg || undefined,
|
||||
},
|
||||
url: url('/timestamp/wait'),
|
||||
body: timestampBody(z, bundle.inputData),
|
||||
});
|
||||
return response.data;
|
||||
const status = response.data || {};
|
||||
const id = status.id || status.jobId;
|
||||
return { ...status, id, jobId: status.jobId || id };
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -18,37 +30,12 @@ module.exports = {
|
|||
display: {
|
||||
label: 'Create Timestamp and Wait',
|
||||
description:
|
||||
'Submits data for blockchain timestamping and waits for the certificate.',
|
||||
'Registers data or a SHA-256 digest and holds the step until the certificate exists. If the chain is slow the step returns status "pending" with the job ID so a later step (or the Timestamp Completed trigger) can finish the Zap.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'data',
|
||||
label: 'Data',
|
||||
type: 'string',
|
||||
required: true,
|
||||
helpText: 'Content to timestamp on the blockchain.',
|
||||
},
|
||||
{
|
||||
key: 'hashAlg',
|
||||
label: 'Hash Algorithm',
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'SHA256',
|
||||
},
|
||||
],
|
||||
inputFields: timestampInputFields(),
|
||||
perform,
|
||||
sample: {
|
||||
id: '550e8400-e29b-41d4-a716-446655440000',
|
||||
status: 'completed',
|
||||
result: 'mock-cert-example',
|
||||
completedAt: '2023-01-01T12:05:00Z',
|
||||
},
|
||||
outputFields: [
|
||||
{ key: 'id', label: 'Job ID' },
|
||||
{ key: 'status', label: 'Status' },
|
||||
{ key: 'result', label: 'Certificate' },
|
||||
{ key: 'completedAt', label: 'Completed At', type: 'datetime' },
|
||||
],
|
||||
sample: statusSample,
|
||||
outputFields: [{ key: 'jobId', label: 'Job ID' }, ...statusOutputFields.slice(1)],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||
/**
|
||||
* `POST /zapier/v1/verify` — verify one certificate.
|
||||
* @module creates/verify_timestamp
|
||||
*/
|
||||
|
||||
const { url, verifyOutputFields, verifySample } = require('../lib/middleware');
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: `${base()}/zapier/v1/verify`,
|
||||
url: url('/verify'),
|
||||
body: { certificate: bundle.inputData.certificate },
|
||||
});
|
||||
return response.data;
|
||||
|
|
@ -14,7 +19,7 @@ module.exports = {
|
|||
noun: 'Verification',
|
||||
display: {
|
||||
label: 'Verify Timestamp',
|
||||
description: 'Verify a timestamp certificate.',
|
||||
description: 'Verifies the authenticity of a timestamp certificate.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
|
|
@ -23,14 +28,11 @@ module.exports = {
|
|||
label: 'Certificate',
|
||||
type: 'text',
|
||||
required: true,
|
||||
helpText: 'The certificate returned when a timestamp job completed.',
|
||||
},
|
||||
],
|
||||
perform,
|
||||
sample: { valid: true, timestamp: '2023-01-01T12:00:00Z', blockIndex: 42 },
|
||||
outputFields: [
|
||||
{ key: 'valid', label: 'Valid', type: 'boolean' },
|
||||
{ key: 'timestamp', label: 'Timestamp', type: 'datetime' },
|
||||
{ key: 'blockIndex', label: 'Block Index', type: 'integer' },
|
||||
],
|
||||
sample: verifySample,
|
||||
outputFields: verifyOutputFields,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
74
index.js
74
index.js
|
|
@ -1,82 +1,100 @@
|
|||
/**
|
||||
* Verae Zapier Platform app definition.
|
||||
*
|
||||
* Every operation calls the Verae middleware (`MIDDLEWARE_BASE_URL`,
|
||||
* `/zapier/v1/*`). The middleware talks to api.veraetime.net; this app never does.
|
||||
* @module index
|
||||
*/
|
||||
|
||||
const authentication = require('./authentication');
|
||||
const { describeError } = require('./lib/middleware');
|
||||
|
||||
const timestampAndWait = require('./creates/timestamp_and_wait');
|
||||
const createTimestamp = require('./creates/create_timestamp');
|
||||
const verifyTimestamp = require('./creates/verify_timestamp');
|
||||
const batchTimestamp = require('./creates/batch_timestamp');
|
||||
const addNumbers = require('./creates/add_numbers');
|
||||
const verifyTimestamp = require('./creates/verify_timestamp');
|
||||
const batchVerify = require('./creates/batch_verify');
|
||||
const batchStatus = require('./creates/batch_status');
|
||||
const getReceipt = require('./creates/get_receipt');
|
||||
|
||||
const jobStatus = require('./searches/job_status');
|
||||
const jobVerification = require('./searches/job_verification');
|
||||
const hashLookup = require('./searches/hash_lookup');
|
||||
const treeLookup = require('./searches/tree_lookup');
|
||||
const findUsage = require('./searches/usage');
|
||||
|
||||
const timestampCompleted = require('./triggers/timestamp_completed');
|
||||
const timestampFailed = require('./triggers/timestamp_failed');
|
||||
|
||||
/**
|
||||
* Attach middleware API key to every outbound request.
|
||||
* Attach the API key to every outbound request, both ways the stack accepts
|
||||
* it: the middleware reads `Authorization: Bearer` or `x-api-key`; the
|
||||
* access planes and the zappier billing edge forward only `x-api-key`.
|
||||
* @param {object} request
|
||||
* @param {object} _z
|
||||
* @param {object} bundle
|
||||
*/
|
||||
const addApiKey = (request, _z, bundle) => {
|
||||
request.headers = request.headers || {};
|
||||
request.headers.Authorization = `Bearer ${bundle.authData.api_key}`;
|
||||
const key = bundle && bundle.authData && bundle.authData.api_key;
|
||||
if (key) {
|
||||
if (!request.headers.Authorization) request.headers.Authorization = `Bearer ${key}`;
|
||||
if (!request.headers['x-api-key']) request.headers['x-api-key'] = key;
|
||||
}
|
||||
return request;
|
||||
};
|
||||
|
||||
/**
|
||||
* Map middleware billing errors to Zapier errors.
|
||||
* Map middleware errors (`{ error, code, details }`) to Zapier errors.
|
||||
* Billing errors carry the upgrade URL; 429 becomes a ThrottledError so
|
||||
* Zapier retries later. Requests made with `skipThrowForStatus` are left alone.
|
||||
* @param {object} response
|
||||
* @param {object} z
|
||||
*/
|
||||
const mapMiddlewareErrors = (response, z) => {
|
||||
if (response.status === 402) {
|
||||
throw new z.errors.Error(
|
||||
`${response.data?.error ?? 'Quota exceeded'}. Upgrade at ${response.data?.details?.upgradeUrl ?? response.data?.upgradeUrl ?? 'your billing portal'}.`,
|
||||
'QuotaExceeded',
|
||||
402,
|
||||
);
|
||||
if (response.skipThrowForStatus || response.status < 400) {
|
||||
return response;
|
||||
}
|
||||
|
||||
if (response.status === 403 && response.data?.code === 'PLAN_UPGRADE_REQUIRED') {
|
||||
throw new z.errors.Error(
|
||||
response.data?.error ?? 'This action requires a paid plan.',
|
||||
'PlanUpgradeRequired',
|
||||
403,
|
||||
);
|
||||
const { message, code, upgradeUrl } = describeError(response);
|
||||
|
||||
if (response.status === 429) {
|
||||
throw new z.errors.ThrottledError(message);
|
||||
}
|
||||
|
||||
return response;
|
||||
const billingCodes = new Set(['QUOTA_EXCEEDED', 'PLAN_UPGRADE_REQUIRED', 'BATCH_LIMIT_EXCEEDED']);
|
||||
if (response.status === 402 || billingCodes.has(code)) {
|
||||
const where = upgradeUrl || 'your billing portal';
|
||||
throw new z.errors.Error(`${message}. Upgrade at ${where}.`, code, response.status);
|
||||
}
|
||||
|
||||
throw new z.errors.Error(message, code, response.status);
|
||||
};
|
||||
|
||||
let platformVersion = '15.19.0';
|
||||
try {
|
||||
platformVersion = require('zapier-platform-core').version;
|
||||
} catch {
|
||||
// optional for unit tests without full install
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
version: require('./package.json').version,
|
||||
platformVersion,
|
||||
platformVersion: require('zapier-platform-core').version,
|
||||
authentication,
|
||||
beforeRequest: [addApiKey],
|
||||
afterResponse: [mapMiddlewareErrors],
|
||||
triggers: {
|
||||
[timestampCompleted.key]: timestampCompleted,
|
||||
[timestampFailed.key]: timestampFailed,
|
||||
},
|
||||
creates: {
|
||||
[addNumbers.key]: addNumbers,
|
||||
[timestampAndWait.key]: timestampAndWait,
|
||||
[createTimestamp.key]: createTimestamp,
|
||||
[verifyTimestamp.key]: verifyTimestamp,
|
||||
[batchTimestamp.key]: batchTimestamp,
|
||||
[verifyTimestamp.key]: verifyTimestamp,
|
||||
[batchVerify.key]: batchVerify,
|
||||
[batchStatus.key]: batchStatus,
|
||||
[getReceipt.key]: getReceipt,
|
||||
},
|
||||
searches: {
|
||||
[jobStatus.key]: jobStatus,
|
||||
[jobVerification.key]: jobVerification,
|
||||
[hashLookup.key]: hashLookup,
|
||||
[treeLookup.key]: treeLookup,
|
||||
[findUsage.key]: findUsage,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
87
lib/hookTrigger.js
Normal file
87
lib/hookTrigger.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Factory for REST Hook triggers backed by the middleware's
|
||||
* `/webhooks/subscribe` and `/webhooks/unsubscribe` endpoints.
|
||||
*
|
||||
* Delivered payload (see verae-middleware inProcessJobPoller / webhookWorker):
|
||||
* `{ event, jobId, tenantId, status: StatusResponse }`.
|
||||
* @module lib/hookTrigger
|
||||
*/
|
||||
|
||||
const { url, statusSample } = require('./middleware');
|
||||
|
||||
/**
|
||||
* @param {object} spec
|
||||
* @param {string} spec.key Zapier trigger key
|
||||
* @param {string} spec.event middleware event name (timestamp.completed | timestamp.failed)
|
||||
* @param {string} spec.label
|
||||
* @param {string} spec.description
|
||||
* @param {object} [spec.sampleStatus] StatusResponse used in the sample payload
|
||||
* @returns {object} Zapier trigger definition
|
||||
*/
|
||||
const hookTrigger = ({ key, event, label, description, sampleStatus = statusSample }) => {
|
||||
const performSubscribe = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: url('/webhooks/subscribe'),
|
||||
body: { targetUrl: bundle.targetUrl, event },
|
||||
});
|
||||
return { id: response.data.id, event };
|
||||
};
|
||||
|
||||
const performUnsubscribe = async (z, bundle) => {
|
||||
const hookId =
|
||||
bundle.subscribeData && typeof bundle.subscribeData === 'object'
|
||||
? bundle.subscribeData.id
|
||||
: bundle.subscribeData;
|
||||
await z.request({
|
||||
method: 'DELETE',
|
||||
url: url('/webhooks/unsubscribe'),
|
||||
body: { hookId, targetUrl: bundle.targetUrl },
|
||||
});
|
||||
return {};
|
||||
};
|
||||
|
||||
/** Zapier dedupes hook items on `id`; the payload only carries `jobId`. */
|
||||
const perform = async (_z, bundle) => {
|
||||
const payload = bundle.cleanedRequest || {};
|
||||
const id = payload.jobId || (payload.status && payload.status.id);
|
||||
return [{ id, ...payload }];
|
||||
};
|
||||
|
||||
/** The middleware has no "list recent events" endpoint; Zapier falls back to `sample`. */
|
||||
const performList = async () => [];
|
||||
|
||||
return {
|
||||
key,
|
||||
noun: 'Timestamp',
|
||||
display: { label, description },
|
||||
operation: {
|
||||
type: 'hook',
|
||||
perform,
|
||||
performList,
|
||||
performSubscribe,
|
||||
performUnsubscribe,
|
||||
sample: {
|
||||
id: sampleStatus.id,
|
||||
event,
|
||||
jobId: sampleStatus.id,
|
||||
tenantId: 'tenant-example',
|
||||
status: sampleStatus,
|
||||
},
|
||||
outputFields: [
|
||||
{ key: 'id', label: 'Job ID' },
|
||||
{ key: 'event', label: 'Event' },
|
||||
{ key: 'jobId', label: 'Job ID' },
|
||||
{ key: 'tenantId', label: 'Tenant ID' },
|
||||
{ key: 'status__status', label: 'Status' },
|
||||
{ key: 'status__result', label: 'Certificate' },
|
||||
{ key: 'status__error', label: 'Error' },
|
||||
{ key: 'status__completedAt', label: 'Completed At', type: 'datetime' },
|
||||
{ key: 'status__sha256', label: 'SHA256' },
|
||||
{ key: 'status__metadata__blockIndex', label: 'Block Index', type: 'integer' },
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = hookTrigger;
|
||||
233
lib/middleware.js
Normal file
233
lib/middleware.js
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/**
|
||||
* Shared helpers for calling the Verae middleware (`/zapier/v1/*`).
|
||||
*
|
||||
* The Zapier app never talks to api.veraetime.net or NATS; the middleware
|
||||
* (verae-middleware) owns API keys, plans/quotas, job watching, webhooks,
|
||||
* and the upstream Verae chain client.
|
||||
* @module lib/middleware
|
||||
*/
|
||||
|
||||
const DEFAULT_BASE_URL = 'http://127.0.0.1:3100';
|
||||
const DEFAULT_PATH_PREFIX = '/zapier/v1';
|
||||
|
||||
/**
|
||||
* Middleware origin, from MIDDLEWARE_BASE_URL (set per Zapier version with
|
||||
* `zapier env:set <version> MIDDLEWARE_BASE_URL=https://...`).
|
||||
* @returns {string} origin without a trailing slash
|
||||
*/
|
||||
const baseUrl = () =>
|
||||
(process.env.MIDDLEWARE_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, '');
|
||||
|
||||
/**
|
||||
* Path prefix in front of every route. The middleware serves `/zapier/v1`;
|
||||
* a fronting edge may mount the same routes elsewhere, so it is overridable
|
||||
* with MIDDLEWARE_PATH_PREFIX.
|
||||
* @returns {string} prefix starting with "/" and without a trailing slash
|
||||
*/
|
||||
const pathPrefix = () => {
|
||||
const raw = process.env.MIDDLEWARE_PATH_PREFIX;
|
||||
if (raw === undefined || raw === null) return DEFAULT_PATH_PREFIX;
|
||||
const trimmed = String(raw).trim().replace(/\/+$/, '');
|
||||
if (!trimmed) return '';
|
||||
return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a middleware URL.
|
||||
* @param {string} path path starting with "/", e.g. "/timestamp/wait"
|
||||
* @returns {string}
|
||||
*/
|
||||
const url = (path) => `${baseUrl()}${pathPrefix()}${path}`;
|
||||
|
||||
/** 64 lowercase/uppercase hex characters. */
|
||||
const SHA256_RE = /^[A-Fa-f0-9]{64}$/;
|
||||
|
||||
/**
|
||||
* Build the body for `/timestamp` and `/timestamp/wait` from Zapier input.
|
||||
* Either `data` or a 64-hex `sha256` is required (the middleware hashes data
|
||||
* itself; sending only the digest is recommended for large files).
|
||||
* Optional public/private metadata dictionaries are passed through.
|
||||
* @param {object} z
|
||||
* @param {object} inputData
|
||||
* @returns {{ data?: string, sha256?: string, hashAlg?: string, publicMetadata?: object, privateMetadata?: object }}
|
||||
*/
|
||||
const timestampBody = (z, inputData) => {
|
||||
const data = inputData.data === undefined || inputData.data === null ? '' : String(inputData.data);
|
||||
const sha256 = String(inputData.sha256 || '').trim().toLowerCase();
|
||||
if (!data && !sha256) {
|
||||
throw new z.errors.Error('Provide either Data or a SHA256 digest.', 'VALIDATION_ERROR', 400);
|
||||
}
|
||||
if (sha256 && !SHA256_RE.test(sha256)) {
|
||||
throw new z.errors.Error('SHA256 must be 64 hexadecimal characters.', 'VALIDATION_ERROR', 400);
|
||||
}
|
||||
const body = { hashAlg: inputData.hashAlg || undefined };
|
||||
if (data) body.data = data;
|
||||
if (sha256) body.sha256 = sha256;
|
||||
const isDict = (v) => v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length > 0;
|
||||
if (isDict(inputData.publicMetadata)) body.publicMetadata = inputData.publicMetadata;
|
||||
if (isDict(inputData.privateMetadata)) body.privateMetadata = inputData.privateMetadata;
|
||||
return body;
|
||||
};
|
||||
|
||||
/** Input fields shared by Create Timestamp and Create Timestamp and Wait. */
|
||||
const timestampInputFields = () => [
|
||||
{
|
||||
key: 'data',
|
||||
label: 'Data',
|
||||
type: 'text',
|
||||
required: false,
|
||||
helpText: 'Content to timestamp. The middleware hashes it with SHA-256; only the digest is sealed on chain. Leave blank if you supply a SHA256 below.',
|
||||
},
|
||||
{
|
||||
key: 'sha256',
|
||||
label: 'SHA256',
|
||||
type: 'string',
|
||||
required: false,
|
||||
helpText: '64 hex characters if you already hashed the file outside Zapier (recommended for large files). Either Data or SHA256 is required.',
|
||||
},
|
||||
hashAlgField,
|
||||
{
|
||||
key: 'publicMetadata',
|
||||
label: 'Public Metadata',
|
||||
dict: true,
|
||||
required: false,
|
||||
helpText: 'Key/value pairs stored with the seal and returned on lookups.',
|
||||
},
|
||||
{
|
||||
key: 'privateMetadata',
|
||||
label: 'Private Metadata',
|
||||
dict: true,
|
||||
required: false,
|
||||
helpText: 'Key/value pairs stored privately with the job (only visible to your account).',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Read the middleware's structured error body (`{ error, code, details }`),
|
||||
* falling back to plain text or a generic message.
|
||||
* @param {object} response zapier-platform-core response
|
||||
* @returns {{ message: string, code: string, upgradeUrl?: string, details?: object }}
|
||||
*/
|
||||
const describeError = (response) => {
|
||||
const data = response.data;
|
||||
if (data && typeof data === 'object' && typeof data.error === 'string') {
|
||||
const details = data.details && typeof data.details === 'object' ? data.details : undefined;
|
||||
return {
|
||||
message: data.error,
|
||||
code: typeof data.code === 'string' ? data.code : `HTTP_${response.status}`,
|
||||
upgradeUrl: details?.upgradeUrl ?? data.upgradeUrl,
|
||||
details,
|
||||
};
|
||||
}
|
||||
|
||||
let text = '';
|
||||
try {
|
||||
text = typeof response.content === 'string' ? response.content.trim() : '';
|
||||
} catch {
|
||||
text = '';
|
||||
}
|
||||
if (text && text.length <= 500 && !text.startsWith('<')) {
|
||||
return { message: text, code: `HTTP_${response.status}` };
|
||||
}
|
||||
return {
|
||||
message: `Verae middleware request failed with HTTP ${response.status}`,
|
||||
code: `HTTP_${response.status}`,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a lookup request for a Zapier search: 404 means "no match" (empty
|
||||
* array), other errors are raised with the middleware's message, and a hit
|
||||
* is returned as a one-element array.
|
||||
* @param {object} z
|
||||
* @param {object} requestOptions passed to z.request
|
||||
* @returns {Promise<object[]>}
|
||||
*/
|
||||
const searchOne = async (z, requestOptions) => {
|
||||
const response = await z.request({ ...requestOptions, skipThrowForStatus: true });
|
||||
if (response.status === 404) return [];
|
||||
if (response.status >= 400) {
|
||||
const { message, code } = describeError(response);
|
||||
throw new z.errors.Error(message, code, response.status);
|
||||
}
|
||||
return [response.data];
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalise a Zapier `list: true` field or newline-separated text into an
|
||||
* array of non-empty trimmed strings.
|
||||
* @param {string[]|string|undefined} value
|
||||
* @returns {string[]}
|
||||
*/
|
||||
const toStringList = (value) => {
|
||||
const raw = Array.isArray(value) ? value : String(value ?? '').split('\n');
|
||||
return raw.map((s) => String(s).trim()).filter(Boolean);
|
||||
};
|
||||
|
||||
/** Sample job status as returned by `/status/{jobId}` and `/timestamp/wait`. */
|
||||
const statusSample = {
|
||||
id: '550e8400-e29b-41d4-a716-446655440000',
|
||||
jobId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
status: 'completed',
|
||||
result: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||
completedAt: '2023-01-01T12:05:00Z',
|
||||
sha256: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
|
||||
metadata: {
|
||||
blockIndex: 42,
|
||||
timestamp: '2023-01-01T12:00:00Z',
|
||||
certificate: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||
sha256: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
|
||||
},
|
||||
};
|
||||
|
||||
/** Output fields for a job status (nested keys use Zapier's `__` notation). */
|
||||
const statusOutputFields = [
|
||||
{ key: 'id', label: 'Job ID' },
|
||||
{ key: 'status', label: 'Status' },
|
||||
{ key: 'error', label: 'Error' },
|
||||
{ key: 'result', label: 'Certificate' },
|
||||
{ key: 'completedAt', label: 'Completed At', type: 'datetime' },
|
||||
{ key: 'sha256', label: 'SHA256' },
|
||||
{ key: 'metadata__blockIndex', label: 'Block Index', type: 'integer' },
|
||||
{ key: 'metadata__timestamp', label: 'Block Timestamp', type: 'datetime' },
|
||||
{ key: 'metadata__certificate', label: 'Certificate (metadata)' },
|
||||
];
|
||||
|
||||
/** Sample verification result from `/verify`. */
|
||||
const verifySample = { valid: true, timestamp: '2023-01-01T12:00:00Z', blockIndex: 42 };
|
||||
|
||||
/** Output fields for a verification result. */
|
||||
const verifyOutputFields = [
|
||||
{ key: 'valid', label: 'Valid', type: 'boolean' },
|
||||
{ key: 'timestamp', label: 'Timestamp', type: 'datetime' },
|
||||
{ key: 'blockIndex', label: 'Block Index', type: 'integer' },
|
||||
];
|
||||
|
||||
/** Hash-algorithm input shared by the timestamp actions. */
|
||||
const hashAlgField = {
|
||||
key: 'hashAlg',
|
||||
label: 'Hash Algorithm',
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'SHA256',
|
||||
helpText: 'Hash algorithm for the chain. Defaults to SHA256.',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_BASE_URL,
|
||||
DEFAULT_PATH_PREFIX,
|
||||
SHA256_RE,
|
||||
baseUrl,
|
||||
describeError,
|
||||
hashAlgField,
|
||||
pathPrefix,
|
||||
searchOne,
|
||||
statusOutputFields,
|
||||
statusSample,
|
||||
timestampBody,
|
||||
timestampInputFields,
|
||||
toStringList,
|
||||
url,
|
||||
verifyOutputFields,
|
||||
verifySample,
|
||||
};
|
||||
819
package-lock.json
generated
Normal file
819
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,819 @@
|
|||
{
|
||||
"name": "verae-zapier",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "verae-zapier",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"zapier-platform-core": "15.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"npm": ">=5.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
||||
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@zapier/secret-scrubber": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@zapier/secret-scrubber/-/secret-scrubber-1.1.6.tgz",
|
||||
"integrity": "sha512-ETblIxh6lnkVHjVqQ8XVB2Ymeo7RudR7kIiZylXtYXAfpmGLXIzbk1h9fjF/SuO21k7jdxaMBaBdqUCBHv8+og==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"create-hash": "1.2.0",
|
||||
"lodash.isplainobject": "4.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"possible-typed-array-names": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
|
||||
"integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"get-intrinsic": "^1.3.0",
|
||||
"set-function-length": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/cipher-base": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz",
|
||||
"integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
"safe-buffer": "^5.2.1",
|
||||
"to-buffer": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/create-hash": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cipher-base": "^1.0.1",
|
||||
"inherits": "^2.0.1",
|
||||
"md5.js": "^1.3.4",
|
||||
"ripemd160": "^2.0.1",
|
||||
"sha.js": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "12.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-12.0.4.tgz",
|
||||
"integrity": "sha512-oWdqbSywffzH1l4WXKPHWA0TWYpqp7IyLfqjipT4upoIFS0HPMqtNotykQpD4iIg0BqtNmdgPCh2WMvMt7yTiw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-callable": "^1.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hash-base": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz",
|
||||
"integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^2.3.8",
|
||||
"safe-buffer": "^5.2.1",
|
||||
"to-buffer": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/is-callable": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-typed-array": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
|
||||
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"which-typed-array": "^1.1.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsonschema": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.2.tgz",
|
||||
"integrity": "sha512-iX5OFQ6yx9NgbHCwse51ohhKgLuLL7Z5cNOeZOPIlDUtAMrxlruHLzVZxbltdHE5mEDXN+75oFOwq6Gn0MZwsA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hash-base": "^3.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"safe-buffer": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-abort-controller": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
||||
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ripemd160": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz",
|
||||
"integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hash-base": "^3.1.2",
|
||||
"inherits": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
|
||||
"integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/sha.js": {
|
||||
"version": "2.4.12",
|
||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz",
|
||||
"integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==",
|
||||
"license": "(MIT AND BSD-3-Clause)",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.4",
|
||||
"safe-buffer": "^5.2.1",
|
||||
"to-buffer": "^1.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"sha.js": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-buffer": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz",
|
||||
"integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"isarray": "^2.0.5",
|
||||
"safe-buffer": "^5.2.1",
|
||||
"typed-array-buffer": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-buffer/node_modules/isarray": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typed-array-buffer": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
||||
"integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.3",
|
||||
"es-errors": "^1.3.0",
|
||||
"is-typed-array": "^1.1.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.22",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz",
|
||||
"integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"available-typed-arrays": "^1.0.7",
|
||||
"call-bind": "^1.0.9",
|
||||
"call-bound": "^1.0.4",
|
||||
"for-each": "^0.3.5",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-tostringtag": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/zapier-platform-core": {
|
||||
"version": "15.19.0",
|
||||
"resolved": "https://registry.npmjs.org/zapier-platform-core/-/zapier-platform-core-15.19.0.tgz",
|
||||
"integrity": "sha512-KWyvuZ+QnooOknFl3AW6HObbXdz1FZYclN46EwUeQEd1xIjHwr+WdQwOYPJJM6jwSBkh0Fb7MMVhZQQ0VmPlCg==",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"@zapier/secret-scrubber": "^1.1.1",
|
||||
"bluebird": "3.7.2",
|
||||
"content-disposition": "0.5.4",
|
||||
"dotenv": "12.0.4 ",
|
||||
"form-data": "4.0.0",
|
||||
"lodash": "4.17.21",
|
||||
"mime-types": "2.1.35",
|
||||
"node-abort-controller": "3.1.1",
|
||||
"node-fetch": "2.6.7",
|
||||
"oauth-sign": "0.9.0",
|
||||
"semver": "7.5.2",
|
||||
"zapier-platform-schema": "15.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16",
|
||||
"npm": ">=5.6.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@types/node": "^20.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/zapier-platform-schema": {
|
||||
"version": "15.19.0",
|
||||
"resolved": "https://registry.npmjs.org/zapier-platform-schema/-/zapier-platform-schema-15.19.0.tgz",
|
||||
"integrity": "sha512-c3lkrIm3/KaAOXGgayhQtXiH7ODtn1lxBTK5marU30yuyO+7WMCpxW+H79yVrfeiDyR7Dhy7LyKtoKw7pEWq+Q==",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"dependencies": {
|
||||
"jsonschema": "1.2.2",
|
||||
"lodash": "4.17.21"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"name": "verae-zapier",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Zapier CLI app for Verae Timestamping via middleware",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "node --test test/**/*.test.js"
|
||||
"test": "node --test test/*.test.js",
|
||||
"test:e2e": "node --test test/e2e.test.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
|
|
|
|||
|
|
@ -1,34 +1,45 @@
|
|||
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||
/**
|
||||
* `GET /zapier/v1/hashes/{sha256}` — find an existing timestamp by the
|
||||
* SHA-256 of its data. 404 means no match.
|
||||
* @module searches/hash_lookup
|
||||
*/
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
try {
|
||||
const response = await z.request({
|
||||
method: 'GET',
|
||||
url: `${base()}/zapier/v1/hashes/${encodeURIComponent(bundle.inputData.sha256)}`,
|
||||
});
|
||||
return [response.data];
|
||||
} catch (err) {
|
||||
if (err.status === 404) return [];
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
const { url, searchOne } = require('../lib/middleware');
|
||||
|
||||
const perform = (z, bundle) =>
|
||||
searchOne(z, { url: url(`/hashes/${encodeURIComponent(bundle.inputData.sha256)}`) });
|
||||
|
||||
module.exports = {
|
||||
key: 'hash_lookup',
|
||||
noun: 'Timestamp',
|
||||
display: {
|
||||
label: 'Find Timestamp by SHA256',
|
||||
description: 'Looks up an existing timestamp for a SHA256 hex digest (mock/middleware).',
|
||||
description: 'Looks up an existing timestamp on the main Verae chain by the SHA256 hex digest of its data.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{ key: 'sha256', label: 'SHA256', type: 'string', required: true },
|
||||
{
|
||||
key: 'sha256',
|
||||
label: 'SHA256',
|
||||
type: 'string',
|
||||
required: true,
|
||||
helpText: '64 hex characters.',
|
||||
},
|
||||
],
|
||||
perform,
|
||||
sample: {
|
||||
sha256: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
|
||||
exists: true,
|
||||
jobId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
timestamp: '2023-01-01T12:05:00Z',
|
||||
status: 'completed',
|
||||
},
|
||||
outputFields: [
|
||||
{ key: 'sha256', label: 'SHA256' },
|
||||
{ key: 'exists', label: 'Exists', type: 'boolean' },
|
||||
{ key: 'jobId', label: 'Job ID' },
|
||||
{ key: 'timestamp', label: 'Timestamp', type: 'datetime' },
|
||||
{ key: 'status', label: 'Status' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,33 +1,26 @@
|
|||
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||
/**
|
||||
* `GET /zapier/v1/status/{jobId}` — 404 means no such job (empty result).
|
||||
* @module searches/job_status
|
||||
*/
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: `${base()}/zapier/v1/status/${encodeURIComponent(bundle.inputData.jobId)}`,
|
||||
});
|
||||
return [response.data];
|
||||
};
|
||||
const { url, searchOne, statusOutputFields, statusSample } = require('../lib/middleware');
|
||||
|
||||
const perform = (z, bundle) =>
|
||||
searchOne(z, { url: url(`/status/${encodeURIComponent(bundle.inputData.jobId)}`) });
|
||||
|
||||
module.exports = {
|
||||
key: 'job_status',
|
||||
noun: 'Job',
|
||||
display: {
|
||||
label: 'Find Job Status',
|
||||
description: 'Look up a timestamp job by ID.',
|
||||
description: 'Looks up a timestamp job by its job ID.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{ key: 'jobId', label: 'Job ID', type: 'string', required: true },
|
||||
],
|
||||
perform,
|
||||
sample: {
|
||||
id: '550e8400-e29b-41d4-a716-446655440000',
|
||||
status: 'completed',
|
||||
result: 'mock-cert',
|
||||
},
|
||||
outputFields: [
|
||||
{ key: 'id', label: 'Job ID' },
|
||||
{ key: 'status', label: 'Status' },
|
||||
{ key: 'result', label: 'Result' },
|
||||
],
|
||||
sample: statusSample,
|
||||
outputFields: statusOutputFields,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue