@manajemin-dev-cli/sdk 0.1.0 → 0.1.2
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 +5 -1
- package/client.d.ts +9 -1
- package/client.js +13 -2
- package/delivery.js +18 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Connect an app to the Manajemin platform: verify what the platform sends you, redeem sign-in codes, acknowledge installs
|
|
4
4
|
and removals, report usage, and keep every database query inside one organisation's workspace. No runtime dependencies.
|
|
5
|
-
Node
|
|
5
|
+
Node 20 or later.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @manajemin-dev-cli/sdk
|
|
9
|
+
```
|
|
6
10
|
|
|
7
11
|
The rules it implements are the published contract in `doc/integration/` (`contract.json`, `openapi.yaml`,
|
|
8
12
|
`signature-vectors.json`), not this code. Its tests run every published signature vector.
|
package/client.d.ts
CHANGED
|
@@ -23,9 +23,17 @@ export interface ClientOptions {
|
|
|
23
23
|
}
|
|
24
24
|
export declare class ManajeminApiError extends Error {
|
|
25
25
|
readonly status: number;
|
|
26
|
+
/** Stable machine code to branch on, e.g. `install_paused` or `rate_limited` (quickstart, "Where to look when something fails"). */
|
|
26
27
|
readonly code: string | null;
|
|
27
28
|
readonly requestId: string | null;
|
|
28
|
-
|
|
29
|
+
/** Seconds to wait before retrying, from `Retry-After` on a 429. */
|
|
30
|
+
readonly retryAfterSeconds: number | null;
|
|
31
|
+
/** The English explanation for the developer. `message` stays the platform's own copy (Indonesian), as in 0.1.1. */
|
|
32
|
+
readonly detail: string | null;
|
|
33
|
+
constructor(status: number, message: string, code: string | null, requestId: string | null, extra?: {
|
|
34
|
+
retryAfterSeconds?: number | null;
|
|
35
|
+
detail?: string | null;
|
|
36
|
+
});
|
|
29
37
|
}
|
|
30
38
|
export interface Claims {
|
|
31
39
|
sub: string;
|
package/client.js
CHANGED
|
@@ -6,14 +6,21 @@
|
|
|
6
6
|
export const DEFAULT_BASE_URL = "https://www.manajemin.com";
|
|
7
7
|
export class ManajeminApiError extends Error {
|
|
8
8
|
status;
|
|
9
|
+
/** Stable machine code to branch on, e.g. `install_paused` or `rate_limited` (quickstart, "Where to look when something fails"). */
|
|
9
10
|
code;
|
|
10
11
|
requestId;
|
|
11
|
-
|
|
12
|
+
/** Seconds to wait before retrying, from `Retry-After` on a 429. */
|
|
13
|
+
retryAfterSeconds;
|
|
14
|
+
/** The English explanation for the developer. `message` stays the platform's own copy (Indonesian), as in 0.1.1. */
|
|
15
|
+
detail;
|
|
16
|
+
constructor(status, message, code, requestId, extra = {}) {
|
|
12
17
|
super(message);
|
|
13
18
|
this.name = "ManajeminApiError";
|
|
14
19
|
this.status = status;
|
|
15
20
|
this.code = code;
|
|
16
21
|
this.requestId = requestId;
|
|
22
|
+
this.retryAfterSeconds = extra.retryAfterSeconds ?? null;
|
|
23
|
+
this.detail = extra.detail ?? null;
|
|
17
24
|
}
|
|
18
25
|
}
|
|
19
26
|
const toStoredFile = (f) => ({ id: f.id, name: f.name, contentType: f.content_type, sizeBytes: f.size_bytes, createdAt: f.created_at });
|
|
@@ -209,7 +216,11 @@ export class ManajeminClient {
|
|
|
209
216
|
}
|
|
210
217
|
if (!response.ok) {
|
|
211
218
|
const error = (parsed ?? {});
|
|
212
|
-
|
|
219
|
+
const retryAfter = Number(response.headers.get("retry-after"));
|
|
220
|
+
throw new ManajeminApiError(response.status, error.error ?? error.message ?? `HTTP ${response.status}`, error.code ?? null, error.requestId ?? null, {
|
|
221
|
+
retryAfterSeconds: Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : null,
|
|
222
|
+
detail: error.message ?? null,
|
|
223
|
+
});
|
|
213
224
|
}
|
|
214
225
|
return (parsed ?? {});
|
|
215
226
|
}
|
package/delivery.js
CHANGED
|
@@ -16,16 +16,29 @@ export function receive(input) {
|
|
|
16
16
|
return { ok: false, status: 400, reason: "malformed" };
|
|
17
17
|
}
|
|
18
18
|
const id = header(input.headers, "x-manajemin-delivery");
|
|
19
|
-
const
|
|
20
|
-
if (!id || !
|
|
19
|
+
const headerEvent = header(input.headers, "x-manajemin-event");
|
|
20
|
+
if (!id || !headerEvent || typeof body !== "object" || body === null)
|
|
21
|
+
return { ok: false, status: 400, reason: "malformed" };
|
|
22
|
+
// The signature covers the body, not the headers: the event, tenant and entity come from the body, and a header that says
|
|
23
|
+
// otherwise means the request was altered after signing (backlog -121), so it is refused.
|
|
24
|
+
const signed = body;
|
|
25
|
+
const fromBody = (value, headerValue) => {
|
|
26
|
+
if (typeof value !== "string")
|
|
27
|
+
return { value: headerValue ?? null, ok: true };
|
|
28
|
+
return { value, ok: headerValue === undefined || headerValue === value };
|
|
29
|
+
};
|
|
30
|
+
const event = fromBody(signed.event, headerEvent);
|
|
31
|
+
const tenant = fromBody(signed.tenant_id, header(input.headers, "x-manajemin-tenant"));
|
|
32
|
+
const entity = fromBody(signed.entity_id, header(input.headers, "x-manajemin-entity"));
|
|
33
|
+
if (!event.ok || !tenant.ok || !entity.ok)
|
|
21
34
|
return { ok: false, status: 400, reason: "malformed" };
|
|
22
35
|
return {
|
|
23
36
|
ok: true,
|
|
24
37
|
delivery: {
|
|
25
38
|
id,
|
|
26
|
-
event,
|
|
27
|
-
tenantId:
|
|
28
|
-
entityId:
|
|
39
|
+
event: event.value ?? headerEvent,
|
|
40
|
+
tenantId: tenant.value,
|
|
41
|
+
entityId: entity.value,
|
|
29
42
|
apiVersion: header(input.headers, "x-manajemin-api-version") ?? null,
|
|
30
43
|
traceId: /^00-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}$/.exec(header(input.headers, "traceparent") ?? "")?.[1] ?? null,
|
|
31
44
|
body: body,
|
package/package.json
CHANGED