proxymail-qa 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,9 @@ const { value: link } = await qa.waitForLink(address, { type: 'reset', timeout:
19
19
  await qa.deleteAddress(address);
20
20
  ```
21
21
 
22
+ With `password: true` the result's `password` is typed as a `string` (pass the
23
+ options inline, or `as const`, so TypeScript sees `true`).
24
+
22
25
  Waits return the code or link on its own, with the sending service and its
23
26
  expiry, never the email's text. They throw `ProxyMailQAError` (status 404) when
24
27
  nothing arrives in time.
@@ -104,6 +107,12 @@ at any time. Retrying won't help until the app can email any address.
104
107
  Mobile apps (XCUITest, Espresso, Maestro, Flutter, Detox and Appium):
105
108
  https://proxymail.qa/docs/mobile
106
109
 
110
+ ## Source
111
+
112
+ https://github.com/aaquib90/proxymail-qa-js. Releases are published to npm from
113
+ GitHub Actions with provenance, so npm shows which commit and workflow built
114
+ each version.
115
+
107
116
  ## License
108
117
 
109
118
  MIT
package/dist/index.d.ts CHANGED
@@ -26,6 +26,15 @@ export interface Address {
26
26
  export interface CreatedAddress extends Address {
27
27
  password?: string;
28
28
  }
29
+ export interface CreatedAddressWithPassword extends CreatedAddress {
30
+ password: string;
31
+ }
32
+ export interface SavedPassword {
33
+ address: string;
34
+ label: string;
35
+ saved: boolean;
36
+ password?: string;
37
+ }
29
38
  export interface Match {
30
39
  address: string;
31
40
  type: 'code' | 'link';
@@ -92,6 +101,32 @@ export interface WaitOptions {
92
101
  since?: Date | string;
93
102
  }
94
103
  export type LinkType = 'verify' | 'magic_signin' | 'invite' | 'reset' | 'any';
104
+ export interface EmailSummary {
105
+ received_at: string;
106
+ service: string | null;
107
+ service_domain: string | null;
108
+ type: 'code' | 'link' | null;
109
+ subtype: string | null;
110
+ email_id: string;
111
+ }
112
+ export interface EmailCount {
113
+ address: string;
114
+ since: string | null;
115
+ count: number;
116
+ emails: EmailSummary[];
117
+ }
118
+ export interface CountOptions {
119
+ since?: Date | string;
120
+ service?: string;
121
+ type?: 'code' | 'link';
122
+ subtype?: CountSubtype | CountSubtype[];
123
+ }
124
+ export type CountSubtype = 'otp' | 'coupon' | 'verify' | 'magic_signin' | 'invite' | 'reset' | 'other' | 'any';
125
+ export interface NoEmail {
126
+ address: string;
127
+ since: string;
128
+ until: string;
129
+ }
95
130
  export declare class ProxyMailQAError extends Error {
96
131
  readonly status: number;
97
132
  readonly code: string;
@@ -107,6 +142,9 @@ export declare class ProxyMailQA {
107
142
  constructor(options?: ClientOptions);
108
143
  private request;
109
144
  project(): Promise<Record<string, unknown>>;
145
+ createAddress(input: CreateAddressInput & {
146
+ password: true;
147
+ }): Promise<CreatedAddressWithPassword>;
110
148
  createAddress(input?: CreateAddressInput): Promise<CreatedAddress>;
111
149
  listAddresses(options?: {
112
150
  limit?: number;
@@ -125,16 +163,21 @@ export declare class ProxyMailQA {
125
163
  includeHtml?: boolean;
126
164
  limit?: number;
127
165
  }): Promise<Record<string, unknown>[]>;
166
+ countEmails(address: string, options?: CountOptions): Promise<EmailCount>;
167
+ expectNoEmail(address: string, options?: CountOptions & {
168
+ timeout?: number;
169
+ }): Promise<NoEmail>;
128
170
  checkDelivery(address: string): Promise<DeliveryStatus>;
129
171
  variants(address: string): Promise<Variants>;
172
+ setPassword(address: string, options: {
173
+ password: string;
174
+ label?: string;
175
+ }): Promise<SavedPassword>;
130
176
  setPassword(address: string, options?: {
131
- password?: string;
177
+ password?: undefined;
132
178
  label?: string;
133
- }): Promise<{
134
- address: string;
135
- label: string;
136
- saved: boolean;
137
- password?: string;
179
+ }): Promise<SavedPassword & {
180
+ password: string;
138
181
  }>;
139
182
  savedPassword(address: string, label?: string): Promise<{
140
183
  address: string;
package/dist/index.js CHANGED
@@ -22,6 +22,19 @@ export class ProxyMailQAError extends Error {
22
22
  }
23
23
  }
24
24
  const TIMED_OUT = /^No (code|link) arrived/;
25
+ function countQuery(options, extra = {}) {
26
+ const query = new URLSearchParams(extra);
27
+ if (options.since !== undefined)
28
+ query.set('since', new Date(options.since).toISOString());
29
+ if (options.service)
30
+ query.set('service', options.service);
31
+ if (options.type)
32
+ query.set('type', options.type);
33
+ const subtype = [options.subtype ?? []].flat().join(',');
34
+ if (subtype)
35
+ query.set('subtype', subtype);
36
+ return query.size ? `?${query}` : '';
37
+ }
25
38
  const MAX_WAIT_SECONDS = 30;
26
39
  export class ProxyMailQA {
27
40
  apiKey;
@@ -107,6 +120,32 @@ export class ProxyMailQA {
107
120
  query.set('limit', String(options.limit));
108
121
  return this.request('GET', `/addresses/${encodeURIComponent(address)}/messages${query.size ? `?${query}` : ''}`);
109
122
  }
123
+ // How many emails reached the address (since a time, from a service, with a
124
+ // code or link), each summarised without its content. Doesn't wait: count
125
+ // after the wait for the first email returns, so a duplicate has had time
126
+ // to arrive. Without since, every email the address has received.
127
+ countEmails(address, options = {}) {
128
+ return this.request('GET', `/addresses/${encodeURIComponent(address)}/count${countQuery(options)}`);
129
+ }
130
+ // Resolves when no matching email arrives within timeout seconds (default
131
+ // 30; longer repeats the request), for checks such as "an unsubscribed user
132
+ // gets nothing". Throws ProxyMailQAError (status 409, code email_arrived)
133
+ // as soon as one does. Pass since as the time of the action, so an email
134
+ // that came before the call still counts; without it, from now.
135
+ async expectNoEmail(address, options = {}) {
136
+ const deadline = Date.now() + (options.timeout ?? MAX_WAIT_SECONDS) * 1000;
137
+ let since = options.since;
138
+ for (;;) {
139
+ const left = Math.max(0, Math.min(MAX_WAIT_SECONDS, Math.ceil((deadline - Date.now()) / 1000)));
140
+ const query = countQuery({ ...options, since }, { timeout: String(left) });
141
+ const result = await this.request('GET', `/addresses/${encodeURIComponent(address)}/none${query}`);
142
+ // The server's since, so later requests watch from the same moment
143
+ // whatever this machine's clock says.
144
+ since = result.since;
145
+ if (Date.now() >= deadline - 500)
146
+ return { address: result.address, since: result.since, until: result.until };
147
+ }
148
+ }
110
149
  // What reached the address and what was refused, without waiting: for when
111
150
  // a wait comes back empty.
112
151
  checkDelivery(address) {
@@ -115,9 +154,6 @@ export class ProxyMailQA {
115
154
  variants(address) {
116
155
  return this.request('GET', `/addresses/${encodeURIComponent(address)}/variants`);
117
156
  }
118
- // Saves the password the test account uses now, after the app under test
119
- // changed or reset it. Without one, a strong password is generated and
120
- // returned this once.
121
157
  setPassword(address, options = {}) {
122
158
  return this.request('PUT', `/addresses/${encodeURIComponent(address)}/password`, { password: options.password, label: options.label });
123
159
  }
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "proxymail-qa",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Disposable test inboxes for Playwright, Cypress and scripts: create an address, then wait for its verification code or link.",
5
5
  "license": "MIT",
6
6
  "author": "Proxy Mail QA <hello@proxymail.qa> (https://proxymail.qa)",
7
- "homepage": "https://proxymail.qa/docs/api",
7
+ "homepage": "https://proxymail.qa/docs/node",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/aaquib90/proxymail-qa-js.git"
11
+ },
8
12
  "bugs": {
13
+ "url": "https://github.com/aaquib90/proxymail-qa-js/issues",
9
14
  "email": "hello@proxymail.qa"
10
15
  },
11
16
  "keywords": [
@@ -39,9 +44,10 @@
39
44
  "build": "tsc -p tsconfig.build.json",
40
45
  "test": "vitest run",
41
46
  "typecheck": "tsc -p tsconfig.json --noEmit",
42
- "prepublishOnly": "pnpm build && pnpm test"
47
+ "prepublishOnly": "npm run build && npm test"
43
48
  },
44
49
  "devDependencies": {
50
+ "typescript": "^5.6.0",
45
51
  "vitest": "^5.0.3"
46
52
  }
47
53
  }