@rubytech/create-maxy-code 0.1.474 → 0.1.475
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +16 -7
- package/payload/platform/plugins/docs/references/google-guide.md +15 -6
- package/payload/platform/plugins/google/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/google/PLUGIN.md +60 -12
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.js +229 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-audit.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js +9 -9
- package/payload/platform/plugins/google/mcp/dist/__tests__/account-register.test.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.js +17 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/consent-url-redirect.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.js +146 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/loopback-listener.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.js +19 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/pending-redirect.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.js +41 -0
- package/payload/platform/plugins/google/mcp/dist/__tests__/register-redirect-chain.test.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.d.ts +44 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.js +112 -0
- package/payload/platform/plugins/google/mcp/dist/auth/account-audit.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.d.ts +17 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.js +143 -0
- package/payload/platform/plugins/google/mcp/dist/auth/loopback-listener.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts +9 -0
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.d.ts.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/auth/pending-store.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/index.js +73 -5
- package/payload/platform/plugins/google/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.d.ts +2 -0
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.d.ts.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.js +37 -0
- package/payload/platform/plugins/google/mcp/dist/scripts/account-audit.js.map +1 -0
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts +20 -4
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.d.ts.map +1 -1
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.js +19 -9
- package/payload/platform/plugins/google/mcp/dist/tools/account-register.js.map +1 -1
- package/payload/platform/plugins/google/references/auth.md +93 -14
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +21 -3
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js +6 -2
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +43 -5
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts +9 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js +78 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-enumerator.js.map +1 -1
- package/payload/server/public/activity.html +1 -1
- package/payload/server/public/assets/activity-Mz1BhgB1.js +1 -0
- package/payload/server/server.js +368 -300
- package/payload/server/public/assets/activity-CdipMTDp.js +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:3f1095125f80c4f095ed4fbe31bf54975c3ef99332fc3958138af6ad9e82b04e
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -2228,14 +2228,23 @@ force Google's annual third-party security assessment. Calendar is only a
|
|
|
2228
2228
|
`google-account-register` returns a consent URL. Relay it to the user: they open
|
|
2229
2229
|
it on any device, sign in, and approve.
|
|
2230
2230
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2231
|
+
What happens next depends on where they approved.
|
|
2232
|
+
|
|
2233
|
+
**If they approved on the machine this install runs on**, the connection
|
|
2234
|
+
completes by itself and their browser shows a page naming the connected account.
|
|
2235
|
+
Nothing is pasted. That page asks them to return to the chat and say so — do
|
|
2236
|
+
that, then call `google-account-register-poll` to confirm.
|
|
2237
|
+
|
|
2238
|
+
**If they approved on another device**, such as a phone, their browser lands on
|
|
2239
|
+
a page that **fails to load**. That is expected, not a fault: the vendor app is
|
|
2240
|
+
a Desktop client, so Google redirects to a loopback address on the install, and
|
|
2241
|
+
a phone has no route to it. The address bar still carries the code. Ask them to
|
|
2242
|
+
copy the **whole URL** from the address bar and pass it to
|
|
2235
2243
|
`google-account-register-submit`, which completes the connection.
|
|
2236
2244
|
|
|
2237
|
-
|
|
2238
|
-
|
|
2245
|
+
Paste the entire URL — the tool pulls the code out. A bare code, a `code=...`
|
|
2246
|
+
fragment, and a value containing `%2F` all work too.
|
|
2247
|
+
`google-account-register-poll` reports the outcome at any point.
|
|
2239
2248
|
|
|
2240
2249
|
The register tool does not block and does not wait. A second register while one
|
|
2241
2250
|
is already live is refused rather than overwriting it; that refusal names how
|
|
@@ -14,14 +14,23 @@ force Google's annual third-party security assessment. Calendar is only a
|
|
|
14
14
|
`google-account-register` returns a consent URL. Relay it to the user: they open
|
|
15
15
|
it on any device, sign in, and approve.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
What happens next depends on where they approved.
|
|
18
|
+
|
|
19
|
+
**If they approved on the machine this install runs on**, the connection
|
|
20
|
+
completes by itself and their browser shows a page naming the connected account.
|
|
21
|
+
Nothing is pasted. That page asks them to return to the chat and say so — do
|
|
22
|
+
that, then call `google-account-register-poll` to confirm.
|
|
23
|
+
|
|
24
|
+
**If they approved on another device**, such as a phone, their browser lands on
|
|
25
|
+
a page that **fails to load**. That is expected, not a fault: the vendor app is
|
|
26
|
+
a Desktop client, so Google redirects to a loopback address on the install, and
|
|
27
|
+
a phone has no route to it. The address bar still carries the code. Ask them to
|
|
28
|
+
copy the **whole URL** from the address bar and pass it to
|
|
21
29
|
`google-account-register-submit`, which completes the connection.
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
Paste the entire URL — the tool pulls the code out. A bare code, a `code=...`
|
|
32
|
+
fragment, and a value containing `%2F` all work too.
|
|
33
|
+
`google-account-register-poll` reports the outcome at any point.
|
|
25
34
|
|
|
26
35
|
The register tool does not block and does not wait. A second register while one
|
|
27
36
|
is already live is refused rather than overwriting it; that refusal names how
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "google",
|
|
3
|
-
"description": "Live Google Calendar for one account via the Google Calendar API v3 — read plus control. Per-account OAuth authorization-code + PKCE against a Desktop client whose id and secret ship in brand config; the plugin mints the consent URL and exchanges the code in-process, and nothing in platform/ui participates. One account can connect several Google accounts; every operational tool takes an optional account selector (the email address) and refuses ambiguously when two or more are connected and none is named. There is deliberately no Gmail surface — mail runs on the `email` plugin's IMAP/SMTP path, and Gmail's restricted scopes would force Google's annual third-party security assessment, whereas Calendar is only a sensitive scope. Tools — google-account-register / google-account-register-submit / google-account-register-poll: connect a Google Calendar (returns a consent URL; the
|
|
3
|
+
"description": "Live Google Calendar for one account via the Google Calendar API v3 — read plus control. Per-account OAuth authorization-code + PKCE against a Desktop client whose id and secret ship in brand config; the plugin mints the consent URL and exchanges the code in-process, and nothing in platform/ui participates. One account can connect several Google accounts; every operational tool takes an optional account selector (the email address) and refuses ambiguously when two or more are connected and none is named. There is deliberately no Gmail surface — mail runs on the `email` plugin's IMAP/SMTP path, and Gmail's restricted scopes would force Google's annual third-party security assessment, whereas Calendar is only a sensitive scope. Tools — google-account-register / google-account-register-submit / google-account-register-poll: connect a Google Calendar (returns a consent URL; approving on the install machine completes by itself and the browser confirms it, while approving on another device lands on a page that fails to load by design and the user pastes the address-bar URL back to submit); google-calendar-list: events in the next rangeDays days, recurring series expanded; google-calendar-event: one event by id; google-calendar-create / google-calendar-update / google-calendar-cancel: control events, attendees notified, update changes only the fields named; google-calendar-respond: accept, decline or tentatively accept an invitation, leaving other attendees' responses untouched; google-calendar-freebusy: busy blocks for this or named calendars; google-account-info: auth state for one connected Google account; google-account-list: every connected Google account with its own status.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: google
|
|
3
|
-
description: Live Google Calendar for one account via the Google Calendar API v3 — read plus control. Per-account OAuth authorization-code + PKCE against a Desktop client whose id and secret ship in brand config; the plugin mints the consent URL and exchanges the code in-process, and nothing in platform/ui participates. One account can connect several Google accounts; every operational tool takes an optional account selector (the email address) and refuses ambiguously when two or more are connected and none is named. There is deliberately no Gmail surface — mail runs on the `email` plugin's IMAP/SMTP path, and Gmail's restricted scopes would force Google's annual third-party security assessment, whereas Calendar is only a sensitive scope. Tools — google-account-register / google-account-register-submit / google-account-register-poll: connect a Google Calendar (returns a consent URL; the
|
|
3
|
+
description: Live Google Calendar for one account via the Google Calendar API v3 — read plus control. Per-account OAuth authorization-code + PKCE against a Desktop client whose id and secret ship in brand config; the plugin mints the consent URL and exchanges the code in-process, and nothing in platform/ui participates. One account can connect several Google accounts; every operational tool takes an optional account selector (the email address) and refuses ambiguously when two or more are connected and none is named. There is deliberately no Gmail surface — mail runs on the `email` plugin's IMAP/SMTP path, and Gmail's restricted scopes would force Google's annual third-party security assessment, whereas Calendar is only a sensitive scope. Tools — google-account-register / google-account-register-submit / google-account-register-poll: connect a Google Calendar (returns a consent URL; approving on the install machine completes by itself and the browser confirms it, while approving on another device lands on a page that fails to load by design and the user pastes the address-bar URL back to submit); google-calendar-list: events in the next rangeDays days, recurring series expanded; google-calendar-event: one event by id; google-calendar-create / google-calendar-update / google-calendar-cancel: control events, attendees notified, update changes only the fields named; google-calendar-respond: accept, decline or tentatively accept an invitation, leaving other attendees' responses untouched; google-calendar-freebusy: busy blocks for this or named calendars; google-account-info: auth state for one connected Google account; google-account-list: every connected Google account with its own status.
|
|
4
4
|
tools:
|
|
5
5
|
- name: google-account-register
|
|
6
6
|
publicAllowlist: false
|
|
@@ -78,19 +78,27 @@ The vendor app is a **Desktop (installed app)** client, the one type whose
|
|
|
78
78
|
secret Google sanctions shipping in distributed software. Its id and secret come
|
|
79
79
|
from `brand.json` and are stamped by the installer together or not at all.
|
|
80
80
|
|
|
81
|
-
`google-account-register` mints the PKCE pair,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
loopback matching applies only to the literal.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
`
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
`google-account-register` mints the PKCE pair, binds a single-shot listener on
|
|
82
|
+
`127.0.0.1` at an OS-assigned port, and returns a consent URL carrying that
|
|
83
|
+
port. The host is always the IP literal, never `localhost` — Google's
|
|
84
|
+
port-agnostic loopback matching applies only to the literal.
|
|
85
|
+
|
|
86
|
+
Consent on the install's own machine reaches that listener, which verifies
|
|
87
|
+
`state`, completes the exchange, and renders a page naming the account. Consent
|
|
88
|
+
on another device cannot reach it, so the redirect fails to load and the user
|
|
89
|
+
pastes the address-bar URL into `google-account-register-submit`. Both paths
|
|
90
|
+
exchange in-process with the stored verifier, resolve the email from the primary
|
|
91
|
+
calendar, and write encrypted tokens. `google-account-register-poll` reports the
|
|
92
|
+
outcome from local state.
|
|
93
|
+
|
|
94
|
+
The pending entry stores the redirect it issued, because the port varies per
|
|
95
|
+
registration and Google compares the exchange's `redirect_uri` byte for byte
|
|
96
|
+
against the one that issued the code. A failed bind falls back to
|
|
97
|
+
`http://127.0.0.1:8571`, leaving the paste path exactly as it was.
|
|
91
98
|
|
|
92
99
|
Token refresh also runs in-process against Google. Nothing in `platform/ui`
|
|
93
|
-
|
|
100
|
+
mints, exchanges or refreshes a Google credential; its only Google surface is the
|
|
101
|
+
standing pending audit below, which reads a file's age and never decrypts.
|
|
94
102
|
|
|
95
103
|
## Scope
|
|
96
104
|
|
|
@@ -111,6 +119,46 @@ is why they are declared in the self-named `${FOO}` form (see
|
|
|
111
119
|
`clientSecretConfigured`; either reading `false` on a brand that ships both is
|
|
112
120
|
the mis-stamp signature.
|
|
113
121
|
|
|
122
|
+
## Observability
|
|
123
|
+
|
|
124
|
+
Two failures here emit no event and do not reproduce on demand: a registration
|
|
125
|
+
the operator abandons, and a refresh token that expires. Nothing touches either;
|
|
126
|
+
they simply stop being true at some future date. Two standing audits report them
|
|
127
|
+
without anything polling.
|
|
128
|
+
|
|
129
|
+
| Audit | Where it runs | Interval | Per-finding line | Heartbeat |
|
|
130
|
+
|---|---|---|---|---|
|
|
131
|
+
| Stranded pending registration | in-process in `platform/ui` | 5 min | `[google-oauth] op=pending-stranded account=<id> ageSec=<N>` | `[google-oauth] op=pending-audit scanned=<N> stranded=<M>` |
|
|
132
|
+
| Refresh-token TTL | spawned `mcp/dist/scripts/account-audit.js` | 1 hour | `[google-account-audit] account=<id> email=<addr> refreshTtlDays=<D> status=<ok\|expiring\|expired>` | `[google-account-audit] op=heartbeat accounts=<N> ok=<A> expiring=<B> expired=<C>` |
|
|
133
|
+
|
|
134
|
+
They run on different mechanisms deliberately. The pending audit needs no
|
|
135
|
+
decrypt — file age alone is conclusive — so it runs in-process and stays
|
|
136
|
+
independent of whether any spawn is healthy. The refresh audit must decrypt the
|
|
137
|
+
token blob with the per-account key, so it runs where `TokenStore` lives.
|
|
138
|
+
|
|
139
|
+
**The heartbeat is the point.** Each audit emits its heartbeat on every cycle,
|
|
140
|
+
including cycles that find nothing, because an audit that has stopped running is
|
|
141
|
+
otherwise indistinguishable from one reporting all-clear. The absence of a
|
|
142
|
+
heartbeat over more than one interval is the documented signal that the audit is
|
|
143
|
+
down — not silence meaning healthy. A failed pending cycle logs
|
|
144
|
+
`op=pending-audit-error`, deliberately a different token from the heartbeat so a
|
|
145
|
+
broken cycle can never be counted as a clean one.
|
|
146
|
+
|
|
147
|
+
`refreshTtlDays` is days left on Google's 180-day **inactivity** revocation
|
|
148
|
+
clock, measured from `lastRefresh`. Google publishes no refresh-token expiry and
|
|
149
|
+
the stored blob holds none, so this is the only bounded, locally-computable
|
|
150
|
+
clock. It does not predict the user revoking access or changing their password,
|
|
151
|
+
neither of which is observable from here. A connection holding no refresh token
|
|
152
|
+
at all reports `expired` with zero days regardless of how recently it was
|
|
153
|
+
written, because it dies when its access token lapses about an hour out.
|
|
154
|
+
|
|
155
|
+
Diagnostic path:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
~/<brand>-code/platform/scripts/logs-read.sh --tail server 200 \
|
|
159
|
+
| grep -E '\[google-oauth\]|\[google-account-audit\]'
|
|
160
|
+
```
|
|
161
|
+
|
|
114
162
|
## Out of scope
|
|
115
163
|
|
|
116
164
|
Gmail (stays on IMAP/SMTP), Contacts, Drive/Docs, push notification channels,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-audit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/account-audit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refresh-TTL audit (Task 1773).
|
|
3
|
+
*
|
|
4
|
+
* The audit exists because a refresh token expiring emits no event and does not
|
|
5
|
+
* reproduce on demand — nothing touches the account, it simply stops working at
|
|
6
|
+
* some future date. So every test here drives the audit ALONE. None of them call
|
|
7
|
+
* a register or poll path first: that is the path that already worked, and a test
|
|
8
|
+
* that primes it proves nothing about the audit.
|
|
9
|
+
*/
|
|
10
|
+
import { strict as assert } from "node:assert";
|
|
11
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { test } from "node:test";
|
|
15
|
+
import { TokenStore } from "../auth/token-store.js";
|
|
16
|
+
import { REFRESH_EXPIRING_THRESHOLD_DAYS, REFRESH_INACTIVITY_LIMIT_DAYS, auditConnectedAccounts, classifyRefresh, runAccountAudit, } from "../auth/account-audit.js";
|
|
17
|
+
const DAY_MS = 86_400_000;
|
|
18
|
+
const ACCOUNT_A = "aaaaaaaa-1111-4111-8111-aaaaaaaaaaaa";
|
|
19
|
+
const ACCOUNT_B = "bbbbbbbb-2222-4222-8222-bbbbbbbbbbbb";
|
|
20
|
+
function freshDir() {
|
|
21
|
+
return mkdtempSync(join(tmpdir(), "google-audit-"));
|
|
22
|
+
}
|
|
23
|
+
/** Write a real encrypted token blob through TokenStore, then rewrite it with a
|
|
24
|
+
* chosen lastRefresh — the audit must read the stored value, not a live clock. */
|
|
25
|
+
function seedAccount(accountsDir, accountId, email, opts, nowMs) {
|
|
26
|
+
const store = new TokenStore(accountId, accountsDir, email);
|
|
27
|
+
store.writeBlobForTest({
|
|
28
|
+
accessToken: "at",
|
|
29
|
+
refreshToken: opts.refreshToken,
|
|
30
|
+
accessTokenExpiry: nowMs + 3_600_000,
|
|
31
|
+
lastRefresh: nowMs - opts.lastRefreshAgeDays * DAY_MS,
|
|
32
|
+
email,
|
|
33
|
+
scopes: ["https://www.googleapis.com/auth/calendar"],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
test("classifyRefresh: a healthy recently-refreshed token is ok", () => {
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
const r = classifyRefresh({
|
|
39
|
+
accessToken: "at",
|
|
40
|
+
refreshToken: "rt",
|
|
41
|
+
accessTokenExpiry: now,
|
|
42
|
+
lastRefresh: now - 3 * DAY_MS,
|
|
43
|
+
email: "a@example.com",
|
|
44
|
+
scopes: [],
|
|
45
|
+
}, now);
|
|
46
|
+
assert.equal(r.status, "ok");
|
|
47
|
+
assert.equal(r.refreshTtlDays, REFRESH_INACTIVITY_LIMIT_DAYS - 3);
|
|
48
|
+
});
|
|
49
|
+
test("classifyRefresh: inside the expiring threshold is expiring, not ok", () => {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
// 175 days idle leaves 5 days of the 180-day inactivity clock.
|
|
52
|
+
const r = classifyRefresh({
|
|
53
|
+
accessToken: "at",
|
|
54
|
+
refreshToken: "rt",
|
|
55
|
+
accessTokenExpiry: now,
|
|
56
|
+
lastRefresh: now - 175 * DAY_MS,
|
|
57
|
+
email: "a@example.com",
|
|
58
|
+
scopes: [],
|
|
59
|
+
}, now);
|
|
60
|
+
assert.equal(r.refreshTtlDays, 5);
|
|
61
|
+
assert.equal(r.status, "expiring");
|
|
62
|
+
assert.ok(r.refreshTtlDays <= REFRESH_EXPIRING_THRESHOLD_DAYS);
|
|
63
|
+
});
|
|
64
|
+
test("classifyRefresh: past the inactivity limit is expired", () => {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const r = classifyRefresh({
|
|
67
|
+
accessToken: "at",
|
|
68
|
+
refreshToken: "rt",
|
|
69
|
+
accessTokenExpiry: now,
|
|
70
|
+
lastRefresh: now - 200 * DAY_MS,
|
|
71
|
+
email: "a@example.com",
|
|
72
|
+
scopes: [],
|
|
73
|
+
}, now);
|
|
74
|
+
assert.equal(r.status, "expired");
|
|
75
|
+
assert.ok(r.refreshTtlDays <= 0);
|
|
76
|
+
});
|
|
77
|
+
test("classifyRefresh: no refresh token at all is expired with zero days", () => {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
// A connection Google issued no refresh token for dies with its access token,
|
|
80
|
+
// about an hour out. It is not 'ok' merely because it was just written.
|
|
81
|
+
const r = classifyRefresh({
|
|
82
|
+
accessToken: "at",
|
|
83
|
+
refreshToken: null,
|
|
84
|
+
accessTokenExpiry: now,
|
|
85
|
+
lastRefresh: now,
|
|
86
|
+
email: "a@example.com",
|
|
87
|
+
scopes: [],
|
|
88
|
+
}, now);
|
|
89
|
+
assert.equal(r.status, "expired");
|
|
90
|
+
assert.equal(r.refreshTtlDays, 0);
|
|
91
|
+
});
|
|
92
|
+
test("classifyRefresh: a lastRefresh in the future never exceeds the documented limit", () => {
|
|
93
|
+
// A device whose clock ran ahead, or a blob restored from such a host. Without
|
|
94
|
+
// the clamp this reports e.g. refreshTtlDays=200, a value the module documents
|
|
95
|
+
// as impossible.
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const r = classifyRefresh({
|
|
98
|
+
accessToken: "at",
|
|
99
|
+
refreshToken: "rt",
|
|
100
|
+
accessTokenExpiry: now,
|
|
101
|
+
lastRefresh: now + 20 * DAY_MS,
|
|
102
|
+
email: "a@example.com",
|
|
103
|
+
scopes: [],
|
|
104
|
+
}, now);
|
|
105
|
+
assert.equal(r.refreshTtlDays, REFRESH_INACTIVITY_LIMIT_DAYS);
|
|
106
|
+
assert.equal(r.status, "ok");
|
|
107
|
+
});
|
|
108
|
+
test("an email carrying whitespace cannot split the audit line into bogus fields", () => {
|
|
109
|
+
const dir = freshDir();
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const lines = [];
|
|
112
|
+
try {
|
|
113
|
+
const store = new TokenStore(ACCOUNT_A, dir, "spacey@example.com");
|
|
114
|
+
store.writeBlobForTest({
|
|
115
|
+
accessToken: "at",
|
|
116
|
+
refreshToken: "rt",
|
|
117
|
+
accessTokenExpiry: now + 3_600_000,
|
|
118
|
+
lastRefresh: now,
|
|
119
|
+
email: "bad id@example.com",
|
|
120
|
+
scopes: [],
|
|
121
|
+
});
|
|
122
|
+
runAccountAudit(dir, now, (l) => lines.push(l));
|
|
123
|
+
const line = lines.find((l) => l.includes("refreshTtlDays="));
|
|
124
|
+
assert.ok(line);
|
|
125
|
+
// status must remain the LAST field and parse cleanly: an unescaped space
|
|
126
|
+
// would push `status=` out of position for any k=v reader.
|
|
127
|
+
const fields = Object.fromEntries(line.split(" ").slice(1).map((p) => p.split("=")));
|
|
128
|
+
assert.equal(fields.status, "ok");
|
|
129
|
+
assert.equal(fields.refreshTtlDays, String(REFRESH_INACTIVITY_LIMIT_DAYS));
|
|
130
|
+
assert.equal(fields.email, "bad_id@example.com");
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
rmSync(dir, { recursive: true, force: true });
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
test("auditConnectedAccounts reports every connected account across accounts", () => {
|
|
137
|
+
const dir = freshDir();
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
try {
|
|
140
|
+
seedAccount(dir, ACCOUNT_A, "one@example.com", { lastRefreshAgeDays: 1, refreshToken: "rt" }, now);
|
|
141
|
+
seedAccount(dir, ACCOUNT_A, "two@example.com", { lastRefreshAgeDays: 175, refreshToken: "rt" }, now);
|
|
142
|
+
seedAccount(dir, ACCOUNT_B, "three@example.com", { lastRefreshAgeDays: 0, refreshToken: null }, now);
|
|
143
|
+
const rows = auditConnectedAccounts(dir, now);
|
|
144
|
+
assert.equal(rows.length, 3);
|
|
145
|
+
const byEmail = new Map(rows.map((r) => [r.email, r]));
|
|
146
|
+
assert.equal(byEmail.get("one@example.com")?.status, "ok");
|
|
147
|
+
assert.equal(byEmail.get("two@example.com")?.status, "expiring");
|
|
148
|
+
assert.equal(byEmail.get("three@example.com")?.status, "expired");
|
|
149
|
+
assert.equal(byEmail.get("one@example.com")?.accountId, ACCOUNT_A);
|
|
150
|
+
assert.equal(byEmail.get("three@example.com")?.accountId, ACCOUNT_B);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
rmSync(dir, { recursive: true, force: true });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
test("one unreadable blob does not blind the scan", () => {
|
|
157
|
+
const dir = freshDir();
|
|
158
|
+
const now = Date.now();
|
|
159
|
+
try {
|
|
160
|
+
seedAccount(dir, ACCOUNT_A, "good@example.com", { lastRefreshAgeDays: 1, refreshToken: "rt" }, now);
|
|
161
|
+
// Corrupt ciphertext under the same account — decrypt will throw.
|
|
162
|
+
const badDir = join(dir, ACCOUNT_A, "secrets", "google", "accounts", "bad@example.com");
|
|
163
|
+
mkdirSync(badDir, { recursive: true });
|
|
164
|
+
writeFileSync(join(badDir, "tokens.enc"), "not:validciphertext");
|
|
165
|
+
const rows = auditConnectedAccounts(dir, now);
|
|
166
|
+
// The healthy account still reports; the corrupt one is skipped, not fatal.
|
|
167
|
+
assert.ok(rows.some((r) => r.email === "good@example.com" && r.status === "ok"));
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
rmSync(dir, { recursive: true, force: true });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
test("non-uuid directories under accountsDir are ignored", () => {
|
|
174
|
+
const dir = freshDir();
|
|
175
|
+
const now = Date.now();
|
|
176
|
+
try {
|
|
177
|
+
mkdirSync(join(dir, "not-an-account", "secrets", "google", "accounts", "x@example.com"), {
|
|
178
|
+
recursive: true,
|
|
179
|
+
});
|
|
180
|
+
assert.deepEqual(auditConnectedAccounts(dir, now), []);
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
rmSync(dir, { recursive: true, force: true });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
test("a missing accounts directory yields no rows and does not throw", () => {
|
|
187
|
+
assert.deepEqual(auditConnectedAccounts(join(tmpdir(), "google-audit-absent-dir"), Date.now()), []);
|
|
188
|
+
});
|
|
189
|
+
test("runAccountAudit emits one line per account plus a heartbeat", () => {
|
|
190
|
+
const dir = freshDir();
|
|
191
|
+
const now = Date.now();
|
|
192
|
+
const lines = [];
|
|
193
|
+
try {
|
|
194
|
+
seedAccount(dir, ACCOUNT_A, "one@example.com", { lastRefreshAgeDays: 1, refreshToken: "rt" }, now);
|
|
195
|
+
seedAccount(dir, ACCOUNT_A, "two@example.com", { lastRefreshAgeDays: 175, refreshToken: "rt" }, now);
|
|
196
|
+
runAccountAudit(dir, now, (l) => lines.push(l));
|
|
197
|
+
const perAccount = lines.filter((l) => l.includes("account=") && !l.includes("op=heartbeat"));
|
|
198
|
+
assert.equal(perAccount.length, 2);
|
|
199
|
+
assert.ok(perAccount.some((l) => l.includes("[google-account-audit]") &&
|
|
200
|
+
l.includes(`account=${ACCOUNT_A}`) &&
|
|
201
|
+
l.includes("email=two@example.com") &&
|
|
202
|
+
l.includes("refreshTtlDays=5") &&
|
|
203
|
+
l.includes("status=expiring")), `expected an expiring line, got: ${perAccount.join(" | ")}`);
|
|
204
|
+
const heartbeat = lines.filter((l) => l.includes("op=heartbeat"));
|
|
205
|
+
assert.equal(heartbeat.length, 1);
|
|
206
|
+
assert.ok(heartbeat[0].includes("accounts=2"));
|
|
207
|
+
assert.ok(heartbeat[0].includes("ok=1"));
|
|
208
|
+
assert.ok(heartbeat[0].includes("expiring=1"));
|
|
209
|
+
assert.ok(heartbeat[0].includes("expired=0"));
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
rmSync(dir, { recursive: true, force: true });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
test("the heartbeat is emitted on a cycle that finds nothing", () => {
|
|
216
|
+
// This is the whole point of the heartbeat: an audit that stopped running must
|
|
217
|
+
// not be indistinguishable from an audit reporting all-clear.
|
|
218
|
+
const dir = freshDir();
|
|
219
|
+
const lines = [];
|
|
220
|
+
try {
|
|
221
|
+
runAccountAudit(dir, Date.now(), (l) => lines.push(l));
|
|
222
|
+
assert.equal(lines.length, 1);
|
|
223
|
+
assert.ok(lines[0].includes("[google-account-audit] op=heartbeat accounts=0"));
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
rmSync(dir, { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
//# sourceMappingURL=account-audit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-audit.test.js","sourceRoot":"","sources":["../../src/__tests__/account-audit.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { createHash } from "node:crypto";
|
|
|
4
4
|
import { mkdtempSync } from "node:fs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
-
import { newPkce, buildConsentUrl, runAccountRegisterStart, runAccountRegisterPoll, listConnected, CALENDAR_SCOPE, LOOPBACK_REDIRECT_URI, normalisePastedCode, runAccountRegisterSubmit, } from "../tools/account-register.js";
|
|
7
|
+
import { newPkce, newState, buildConsentUrl, runAccountRegisterStart, runAccountRegisterPoll, listConnected, CALENDAR_SCOPE, LOOPBACK_REDIRECT_URI, normalisePastedCode, runAccountRegisterSubmit, } from "../tools/account-register.js";
|
|
8
8
|
import { TokenStore } from "../auth/token-store.js";
|
|
9
9
|
import { PendingStore } from "../auth/pending-store.js";
|
|
10
10
|
function newDir() {
|
|
@@ -20,7 +20,7 @@ test("consent URL carries offline access, forced consent, and the calendar scope
|
|
|
20
20
|
clientId: "cid",
|
|
21
21
|
state: "st",
|
|
22
22
|
challenge: "ch",
|
|
23
|
-
|
|
23
|
+
redirectUri: LOOPBACK_REDIRECT_URI,
|
|
24
24
|
}));
|
|
25
25
|
assert.equal(url.searchParams.get("access_type"), "offline");
|
|
26
26
|
assert.equal(url.searchParams.get("prompt"), "consent");
|
|
@@ -37,7 +37,7 @@ test("redirect_uri carries no query string at all", () => {
|
|
|
37
37
|
clientId: "cid",
|
|
38
38
|
state: "nonce",
|
|
39
39
|
challenge: "ch",
|
|
40
|
-
|
|
40
|
+
redirectUri: LOOPBACK_REDIRECT_URI,
|
|
41
41
|
}));
|
|
42
42
|
const redirect = url.searchParams.get("redirect_uri");
|
|
43
43
|
assert.ok(!redirect.includes("?"), `redirect_uri must have no query: ${redirect}`);
|
|
@@ -45,7 +45,7 @@ test("redirect_uri carries no query string at all", () => {
|
|
|
45
45
|
});
|
|
46
46
|
test("register stores the same opaque state it sends, and no account id", async () => {
|
|
47
47
|
const dir = newDir();
|
|
48
|
-
const res = await runAccountRegisterStart({ accountId: "acct1", accountsDir: dir, clientId: "cid" });
|
|
48
|
+
const res = await runAccountRegisterStart({ accountId: "acct1", accountsDir: dir, clientId: "cid", state: newState(), redirectUri: LOOPBACK_REDIRECT_URI });
|
|
49
49
|
const sent = new URL(res.consentUrl).searchParams.get("state");
|
|
50
50
|
const { PendingStore: PS } = await import("../auth/pending-store.js");
|
|
51
51
|
assert.equal(new PS("acct1", dir).read()?.state, sent, "pending must hold exactly what was sent");
|
|
@@ -53,7 +53,7 @@ test("register stores the same opaque state it sends, and no account id", async
|
|
|
53
53
|
});
|
|
54
54
|
test("a second register while one is live is refused as in-progress", async () => {
|
|
55
55
|
const dir = newDir();
|
|
56
|
-
const args = { accountId: "acct1", accountsDir: dir, clientId: "cid" };
|
|
56
|
+
const args = { accountId: "acct1", accountsDir: dir, clientId: "cid", state: newState(), redirectUri: LOOPBACK_REDIRECT_URI };
|
|
57
57
|
const first = await runAccountRegisterStart(args);
|
|
58
58
|
assert.equal(first.status, "pending");
|
|
59
59
|
assert.ok(first.consentUrl);
|
|
@@ -67,7 +67,7 @@ test("poll reports none with nothing connected and nothing pending", async () =>
|
|
|
67
67
|
});
|
|
68
68
|
test("poll reports pending while a fresh registration is live", async () => {
|
|
69
69
|
const dir = newDir();
|
|
70
|
-
await runAccountRegisterStart({ accountId: "a", accountsDir: dir, clientId: "c" });
|
|
70
|
+
await runAccountRegisterStart({ accountId: "a", accountsDir: dir, clientId: "c", state: newState(), redirectUri: LOOPBACK_REDIRECT_URI });
|
|
71
71
|
const res = await runAccountRegisterPoll({ accountId: "a", accountsDir: dir });
|
|
72
72
|
assert.equal(res.status, "pending");
|
|
73
73
|
});
|
|
@@ -80,7 +80,7 @@ test("listConnected reports a stored account by email", () => {
|
|
|
80
80
|
assert.equal(connected[0].status, "ok");
|
|
81
81
|
});
|
|
82
82
|
test("consent redirect is the loopback IP literal, never localhost or https", () => {
|
|
83
|
-
const url = new URL(buildConsentUrl({ clientId: "cid", state: "nonce", challenge: "ch",
|
|
83
|
+
const url = new URL(buildConsentUrl({ clientId: "cid", state: "nonce", challenge: "ch", redirectUri: LOOPBACK_REDIRECT_URI }));
|
|
84
84
|
const r = url.searchParams.get("redirect_uri");
|
|
85
85
|
assert.equal(r, LOOPBACK_REDIRECT_URI);
|
|
86
86
|
assert.equal(r, "http://127.0.0.1:8571");
|
|
@@ -116,7 +116,7 @@ test("normalisePastedCode never throws on a malformed percent escape", () => {
|
|
|
116
116
|
});
|
|
117
117
|
test("submit exchanges with the identical loopback redirect and stores the email", async () => {
|
|
118
118
|
const dir = newDir();
|
|
119
|
-
await runAccountRegisterStart({ accountId: "a", accountsDir: dir, clientId: "cid" });
|
|
119
|
+
await runAccountRegisterStart({ accountId: "a", accountsDir: dir, clientId: "cid", state: newState(), redirectUri: LOOPBACK_REDIRECT_URI });
|
|
120
120
|
const calls = [];
|
|
121
121
|
const fake = (async (url, init = {}) => {
|
|
122
122
|
calls.push({ url: String(url), body: String(init.body ?? "") });
|
|
@@ -141,7 +141,7 @@ test("submit refuses when no registration is pending", async () => {
|
|
|
141
141
|
});
|
|
142
142
|
test("submit preserves the Google error body on a failed exchange", async () => {
|
|
143
143
|
const dir = newDir();
|
|
144
|
-
await runAccountRegisterStart({ accountId: "a", accountsDir: dir, clientId: "cid" });
|
|
144
|
+
await runAccountRegisterStart({ accountId: "a", accountsDir: dir, clientId: "cid", state: newState(), redirectUri: LOOPBACK_REDIRECT_URI });
|
|
145
145
|
const fake = (async () => new Response('{"error":"invalid_grant"}', { status: 400 }));
|
|
146
146
|
await assert.rejects(() => runAccountRegisterSubmit({
|
|
147
147
|
accountId: "a", accountsDir: dir, clientId: "c", clientSecret: "s", code: "4/0x", fetchImpl: fake,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-register.test.js","sourceRoot":"","sources":["../../src/__tests__/account-register.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,OAAO,EACP,eAAe,EACf,uBAAuB,EACvB,sBAAsB,EACtB,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,SAAS,MAAM;IACb,OAAO,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACnE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,EAAE,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;IACnF,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AACnC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4EAA4E,EAAE,GAAG,EAAE;IACtF,MAAM,GAAG,GAAG,IAAI,GAAG,CACjB,eAAe,CAAC;QACd,QAAQ,EAAE,KAAK;QACf,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,IAAI;QACf,
|
|
1
|
+
{"version":3,"file":"account-register.test.js","sourceRoot":"","sources":["../../src/__tests__/account-register.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent-url-redirect.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/consent-url-redirect.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { buildConsentUrl } from "../tools/account-register.js";
|
|
4
|
+
test("the consent URL carries the redirect it was given", () => {
|
|
5
|
+
const u = new URL(buildConsentUrl({ clientId: "cid", state: "s", challenge: "c", redirectUri: "http://127.0.0.1:54321" }));
|
|
6
|
+
assert.equal(u.searchParams.get("redirect_uri"), "http://127.0.0.1:54321");
|
|
7
|
+
});
|
|
8
|
+
test("the consent URL never uses the localhost hostname", () => {
|
|
9
|
+
const u = buildConsentUrl({
|
|
10
|
+
clientId: "cid",
|
|
11
|
+
state: "s",
|
|
12
|
+
challenge: "c",
|
|
13
|
+
redirectUri: "http://127.0.0.1:8571",
|
|
14
|
+
});
|
|
15
|
+
assert.ok(!u.includes("localhost"));
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=consent-url-redirect.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent-url-redirect.test.js","sourceRoot":"","sources":["../../src/__tests__/consent-url-redirect.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,MAAM,CAAC,GAAG,IAAI,GAAG,CACf,eAAe,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,EAAE,wBAAwB,EAAE,CAAC,CACxG,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,wBAAwB,CAAC,CAAC;AAC7E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,MAAM,CAAC,GAAG,eAAe,CAAC;QACxB,QAAQ,EAAE,KAAK;QACf,KAAK,EAAE,GAAG;QACV,SAAS,EAAE,GAAG;QACd,WAAW,EAAE,uBAAuB;KACrC,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback-listener.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/loopback-listener.test.ts"],"names":[],"mappings":""}
|