@godxjp/server-sdk 0.11.0 → 0.13.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/dist/http.d.ts CHANGED
@@ -62,12 +62,17 @@ declare class GodxHttpError extends Error {
62
62
  declare function loadIssuer(issuerUrl: string, options?: HttpOptions): Promise<Issuer>;
63
63
  interface SsoClientConfig {
64
64
  issuer: string;
65
- serviceSlug: string;
65
+ /**
66
+ * Catalog service slug, sent as `service_slug`. OMIT it for a mini app (`org_…`/`isv_…`
67
+ * client): the Platform routes to the mini-app lane only when `service_slug` is absent —
68
+ * the same rule as php-sdk's BrowserProfile/BrowserServer (`serviceSlug === null` → omitted).
69
+ */
70
+ serviceSlug?: string;
66
71
  clientId: string;
67
72
  /** Omit for a public (secretless) instance — PKCE alone then. */
68
73
  clientSecret?: string;
69
74
  redirectUri: string;
70
- /** Token audience = the ServiceInstance slug (defaults to serviceSlug). */
75
+ /** Token audience = the ServiceInstance slug (defaults to serviceSlug; a mini app: its client_id). */
71
76
  audience?: string;
72
77
  /** Headers, deadline and fetch for every request this client makes. */
73
78
  http?: HttpOptions;
@@ -109,6 +114,7 @@ declare class GodxSsoClient {
109
114
  userinfo(accessToken: string): Promise<UserinfoResponse>;
110
115
  private tokenRequest;
111
116
  private postForm;
117
+ private serviceSlugField;
112
118
  }
113
119
  /**
114
120
  * Server-to-server reads with service credentials (X-Service-Slug/X-Service-Secret).
package/dist/http.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  createServiceApi,
6
6
  loadIssuer,
7
7
  platformFetch
8
- } from "./chunk-2N6BMNKT.js";
8
+ } from "./chunk-DUFW6IQK.js";
9
9
  export {
10
10
  GodxHttpError,
11
11
  GodxSsoClient,
@@ -0,0 +1,269 @@
1
+ import { HttpOptions, GodxHttpError } from './http.js';
2
+ import 'jose';
3
+
4
+ type NoticeLocale = "ja" | "en" | "vi";
5
+ /** Copy in the locales Platform renders: `ja` required, `en`/`vi` optional. Plain text only. */
6
+ interface LocalizedText {
7
+ readonly ja: string;
8
+ readonly en?: string | null;
9
+ readonly vi?: string | null;
10
+ }
11
+ /** Platform's closed categories; `service` is the one for a notice a service authors. */
12
+ type NoticeCategory = "service" | "organization_invitation" | "access_request" | "service_provisioning" | "billing" | "security" | "operator";
13
+ type NoticeActionStyle = "default" | "primary" | "danger";
14
+ /** Whether a press carries a reason; Platform enforces it before calling the service. */
15
+ type NoticeActionReason = "none" | "optional" | "required";
16
+ /** One quick-action button (at most three on a notice). Only a service instance may offer them. */
17
+ interface NoticeActionButton {
18
+ readonly id: string;
19
+ readonly label: LocalizedText;
20
+ readonly style?: NoticeActionStyle;
21
+ readonly reason?: NoticeActionReason;
22
+ }
23
+ /** Platform's wire body, in the shape both SDKs emit: only fields that were set. */
24
+ interface NoticePayload {
25
+ recipients: string[];
26
+ category: NoticeCategory;
27
+ source_ref: string;
28
+ action_url: string;
29
+ title: Partial<Record<NoticeLocale, string>> & {
30
+ ja: string;
31
+ };
32
+ body: Partial<Record<NoticeLocale, string>> & {
33
+ ja: string;
34
+ };
35
+ kind?: string;
36
+ context?: Partial<Record<NoticeLocale, string>> & {
37
+ ja: string;
38
+ };
39
+ actions?: {
40
+ id: string;
41
+ label: Partial<Record<NoticeLocale, string>> & {
42
+ ja: string;
43
+ };
44
+ style: NoticeActionStyle;
45
+ reason: NoticeActionReason;
46
+ }[];
47
+ organization_id?: string;
48
+ }
49
+ /** A notice Platform would refuse (or silently drop part of); `field` is Laravel's dot-notation key. */
50
+ declare class InvalidNoticeError extends Error {
51
+ readonly field: string;
52
+ constructor(field: string, reason: string);
53
+ }
54
+ interface NoticeFields {
55
+ readonly recipients: readonly string[];
56
+ readonly sourceRef: string;
57
+ readonly actionUrl: string;
58
+ readonly title: LocalizedText;
59
+ readonly body: LocalizedText;
60
+ readonly category: NoticeCategory;
61
+ readonly kind: string | null;
62
+ readonly context: LocalizedText | null;
63
+ readonly actions: readonly NoticeActionButton[];
64
+ readonly organizationId: string | null;
65
+ }
66
+ /**
67
+ * One notice, immutable: each `with…()` returns a new one, checked again.
68
+ *
69
+ * ```ts
70
+ * const notice = Notice.create({
71
+ * recipients: [approverId], // Platform user ids, 1..100
72
+ * sourceRef: `approval:task:${taskId}`, // the dedup key: one row per ref per person
73
+ * actionUrl: `${baseUrl}/requests/42`, // absolute, under the instance base URL
74
+ * title: { ja: "承認依頼", en: "Approval requested" },
75
+ * body: { ja: "休暇申請 #42 が届きました。" },
76
+ * })
77
+ * .withKind("approval")
78
+ * .withActions(
79
+ * { id: "approve", label: { ja: "承認", en: "Approve" }, style: "primary" },
80
+ * { id: "reject", label: { ja: "却下", en: "Reject" }, style: "danger", reason: "required" },
81
+ * );
82
+ * ```
83
+ *
84
+ * Every recipient must be a member of the organization with access to the service, or Platform
85
+ * refuses the WHOLE call (422) — checked there, not here. Sending the same `sourceRef` again
86
+ * rewrites that person's row and marks it unread, without a second email.
87
+ */
88
+ declare class Notice implements NoticeFields {
89
+ readonly recipients: readonly string[];
90
+ readonly sourceRef: string;
91
+ readonly actionUrl: string;
92
+ readonly title: LocalizedText;
93
+ readonly body: LocalizedText;
94
+ readonly category: NoticeCategory;
95
+ readonly kind: string | null;
96
+ readonly context: LocalizedText | null;
97
+ readonly actions: readonly NoticeActionButton[];
98
+ readonly organizationId: string | null;
99
+ private constructor();
100
+ /** @throws InvalidNoticeError */
101
+ static create(input: {
102
+ recipients: readonly string[];
103
+ sourceRef: string;
104
+ actionUrl: string;
105
+ title: LocalizedText;
106
+ body: LocalizedText;
107
+ }): Notice;
108
+ /**
109
+ * Read a body in Platform's wire shape. Stricter than Platform on purpose: an unknown key or
110
+ * locale, which Platform would silently drop, is refused.
111
+ *
112
+ * @throws InvalidNoticeError
113
+ */
114
+ static fromPayload(payload: unknown): Notice;
115
+ withCategory(category: NoticeCategory): Notice;
116
+ /** One of the kinds the service declared in its manifest (`notifications`), so a person can turn it off. */
117
+ withKind(kind: string | null): Notice;
118
+ /** A short line placing the notice, printed under the email body (e.g. `WEB-123 · Website`). */
119
+ withContext(context: LocalizedText | null): Notice;
120
+ /** Replaces the quick actions (at most three). A notice with actions is never mirrored to Renraku. */
121
+ withActions(...actions: NoticeActionButton[]): Notice;
122
+ /** The CONSOLE organization id the notice is about — required for a deployment serving many organizations. */
123
+ forOrganization(consoleOrganizationId: string | null): Notice;
124
+ /**
125
+ * Platform's body, with only the fields that were set: an absent optional field and an empty one
126
+ * are not the same body to Platform's Idempotency-Key fingerprint.
127
+ */
128
+ toPayload(): NoticePayload;
129
+ private assertValid;
130
+ }
131
+ /** Why a notice was not delivered — the same strings as php-sdk's `NotificationFailure`. */
132
+ type NotificationFailureReason =
133
+ /** No instance credential was configured, so nothing was sent. */
134
+ "not_configured"
135
+ /** 401. */
136
+ | "unauthenticated"
137
+ /** 403: `notifications_api_enabled` is off (default) or the credential is wrong — Platform answers both alike. */
138
+ | "forbidden"
139
+ /** 403 `ORGANIZATION_NOT_SERVED`: forOrganization() names one this instance does not serve. */
140
+ | "organization_not_served"
141
+ /** 409: this Idempotency-Key was already used with another body. */
142
+ | "idempotency_conflict"
143
+ /** 422: Platform refused the body — usually one recipient without access, which refuses the whole call. */
144
+ | "invalid" | "rate_limited"
145
+ /** 5xx, a timeout or a network failure. The notice may or may not have been written. */
146
+ | "unavailable"
147
+ /** A redirect, an unexpected status, or a success whose body is not Platform's shape. */
148
+ | "unexpected_response";
149
+ /**
150
+ * A notice was not delivered. A {@link GodxHttpError}, so an existing catch keeps working; its
151
+ * `body` is always empty — like php-sdk, this client never reads an error body (Platform's have
152
+ * carried internal detail), only the `X-Godx-Error` code and `X-Request-Id`.
153
+ */
154
+ declare class NotificationError extends GodxHttpError {
155
+ readonly reason: NotificationFailureReason;
156
+ readonly requestId: string | null;
157
+ constructor(reason: NotificationFailureReason, status?: number, requestId?: string | null, options?: {
158
+ cause?: unknown;
159
+ });
160
+ /**
161
+ * Whether sending again may succeed. Retry only with the SAME idempotency key and notice: an
162
+ * unavailable answer can follow a write that happened.
163
+ */
164
+ get retryable(): boolean;
165
+ }
166
+ /** Platform's answer: rows inserted, rows rewritten (same `source_ref`), people skipped (`preference_off`). */
167
+ interface NoticeDelivery {
168
+ readonly created: readonly {
169
+ id: string;
170
+ user_id: string;
171
+ }[];
172
+ readonly updated: readonly {
173
+ id: string;
174
+ user_id: string;
175
+ }[];
176
+ readonly skipped: readonly {
177
+ user_id: string;
178
+ reason: string;
179
+ }[];
180
+ }
181
+ interface NotificationClientConfig {
182
+ issuer: string;
183
+ /** The INSTANCE slug (the token audience), never the client_id. */
184
+ serviceSlug: string;
185
+ /** The instance client_secret. */
186
+ serviceSecret: string;
187
+ /** Headers, deadline and fetch for every call. */
188
+ http?: HttpOptions;
189
+ }
190
+ interface NotificationClient {
191
+ /** Whether a credential is configured; without one, send() rejects with `not_configured`. */
192
+ readonly configured: boolean;
193
+ /**
194
+ * `idempotencyKey` (at most 191 characters): the same key and notice replay Platform's stored
195
+ * answer; the same key with another notice is `idempotency_conflict`.
196
+ */
197
+ send(notice: Notice, options?: {
198
+ idempotencyKey?: string;
199
+ }): Promise<NoticeDelivery>;
200
+ }
201
+ declare const NOTIFICATIONS_PATH = "/api/sso/service/notifications";
202
+ /**
203
+ * The machine lane — the instance credential, gated by its capability `notifications_api_enabled`
204
+ * (default off). Redirects are never followed: a credential header must not reach another origin.
205
+ */
206
+ declare function createNotificationClient(config: NotificationClientConfig): NotificationClient;
207
+ /**
208
+ * The service's answer to a press. `done` and `already_done` SETTLE the notice: Platform remembers
209
+ * it and replays it on the next press without calling again — answer `already_done` only when the
210
+ * effect is confirmed.
211
+ */
212
+ type NoticeActionState = "done" | "already_done" | "denied" | "invalid";
213
+ interface NoticeActionResult {
214
+ readonly state: NoticeActionState;
215
+ /** One short line for the person, in the press's locale (Platform shows at most 500 characters). */
216
+ readonly message?: string | null;
217
+ }
218
+ /** One press, after the token was verified as Platform's notice-action token for a person. */
219
+ interface NoticeAction {
220
+ /** Who pressed (`sub`) and in which organization (`organization_context_id`, the console id) — from the token, never the body. */
221
+ readonly principal: {
222
+ readonly subject: string;
223
+ readonly organization: string;
224
+ readonly clientId: string;
225
+ readonly scopes: readonly string[];
226
+ };
227
+ readonly sourceRef: string;
228
+ /** The button id. */
229
+ readonly action: string;
230
+ /** Null when none was written. */
231
+ readonly reason: string | null;
232
+ /** The language to answer `message` in. */
233
+ readonly locale: NoticeLocale | null;
234
+ readonly noticeId: string | null;
235
+ }
236
+ /**
237
+ * Platform decided NOTHING: it checked only that the person may still open the service and that the
238
+ * notice offered this button. The handler, as `press.principal` and under the service's own rules,
239
+ * finds the subject by `sourceRef` WITHIN `press.principal.organization`, answers `invalid` for an
240
+ * action it does not offer, re-checks the person's right (`denied`), and makes the change
241
+ * atomically. A thrown error is a 500, which the person sees as a failure; nothing is settled.
242
+ */
243
+ type NoticeActionHandler = (press: NoticeAction) => NoticeActionResult | Promise<NoticeActionResult>;
244
+ declare const NOTICE_ACTIONS_PATH = "/api/platform/v1/notice-actions";
245
+ interface NoticeActionOptions {
246
+ issuer: string;
247
+ /** Your instance slug — the token's `aud`. */
248
+ audience: string;
249
+ handler: NoticeActionHandler;
250
+ /** Default `platform-notice-actions`. */
251
+ clientId?: string;
252
+ /** Default `notices.actions:execute`. */
253
+ scope?: string;
254
+ /** Headers, deadline and fetch for discovery and JWKS. */
255
+ http?: HttpOptions;
256
+ }
257
+ /**
258
+ * Answers Platform's `POST {base_url}/api/platform/v1/notice-actions` (Fetch API: Workers, Node 18+;
259
+ * Hono: `app.post(NOTICE_ACTIONS_PATH, (c) => handleNoticeAction(c.req.raw, options))`).
260
+ *
261
+ * The token is verified like php-sdk's `AuthenticateResource` does (RS256, `typ` at+jwt, exact
262
+ * issuer from discovery, `aud`, `exp`; else 401) — not with {@link verifyAccessToken}, because
263
+ * Platform mints this token without `organization_id`. Then it must be a PERSON's (`prn` user)
264
+ * from `clientId` carrying `scope`, else `403 {"error":"forbidden"}`; a malformed body is
265
+ * `422 {"state":"invalid"}`. The handler's answer is `{state, message?}`: 200, or 422 for `invalid`.
266
+ */
267
+ declare function handleNoticeAction(request: Request, options: NoticeActionOptions): Promise<Response>;
268
+
269
+ export { InvalidNoticeError, type LocalizedText, NOTICE_ACTIONS_PATH, NOTIFICATIONS_PATH, Notice, type NoticeAction, type NoticeActionButton, type NoticeActionHandler, type NoticeActionOptions, type NoticeActionReason, type NoticeActionResult, type NoticeActionState, type NoticeActionStyle, type NoticeCategory, type NoticeDelivery, type NoticeLocale, type NoticePayload, type NotificationClient, type NotificationClientConfig, NotificationError, type NotificationFailureReason, createNotificationClient, handleNoticeAction };