@feelflow/ffid-sdk 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/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, ComponentType, FC } from 'react';
3
- import { F as FFIDConfig, a as FFIDUser, b as FFIDOrganization, c as FFIDError, d as FFIDSubscriptionContextValue } from './index-CtBBLbTn.js';
4
- export { e as FFIDApiResponse, f as FFIDContextValue, g as FFIDLogger, h as FFIDLoginButton, i as FFIDOrganizationSwitcher, j as FFIDSessionResponse, k as FFIDSubscription, l as FFIDSubscriptionBadge, m as FFIDUserMenu } from './index-CtBBLbTn.js';
3
+ import { F as FFIDConfig, a as FFIDUser, b as FFIDOrganization, c as FFIDError, d as FFIDSubscriptionContextValue, e as FFIDAnnouncementsClientConfig, L as ListAnnouncementsOptions, f as FFIDAnnouncementsApiResponse, A as AnnouncementListResponse, g as FFIDAnnouncementsLogger } from './index-B92_OuFc.js';
4
+ export { h as Announcement, i as AnnouncementStatus, j as AnnouncementType, k as FFIDAnnouncementBadge, l as FFIDAnnouncementList, m as FFIDAnnouncementsError, n as FFIDAnnouncementsErrorCode, o as FFIDAnnouncementsServerResponse, p as FFIDApiResponse, q as FFIDContextValue, r as FFIDLogger, s as FFIDLoginButton, t as FFIDOrganizationSwitcher, u as FFIDSessionResponse, v as FFIDSubscription, w as FFIDSubscriptionBadge, x as FFIDUserMenu, U as UseFFIDAnnouncementsOptions, y as UseFFIDAnnouncementsReturn, z as useFFIDAnnouncements } from './index-B92_OuFc.js';
5
5
 
6
6
  /**
7
7
  * FFID SDK Shared Constants
8
8
  *
9
- * Constants shared across all SDK entry points (main client + legal client).
9
+ * Constants shared across all SDK entry points.
10
10
  * Centralizing these prevents drift during domain/URL changes.
11
11
  */
12
12
  /** Default FFID API base URL (production) */
@@ -178,4 +178,59 @@ interface WithFFIDAuthOptions {
178
178
  */
179
179
  declare function withFFIDAuth<P extends object>(Component: ComponentType<P>, options?: WithFFIDAuthOptions): FC<P>;
180
180
 
181
- export { DEFAULT_API_BASE_URL, FFIDConfig, FFIDError, FFIDOrganization, FFIDProvider, type FFIDProviderProps, FFIDSubscriptionContextValue, FFIDUser, type UseFFIDReturn, type WithFFIDAuthOptions, type WithSubscriptionOptions, useFFID, useSubscription, withFFIDAuth, withSubscription };
181
+ /**
182
+ * FFID Announcements API Client
183
+ *
184
+ * Handles API communication for public announcements.
185
+ * No authentication required - this is a public endpoint.
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * import { createFFIDAnnouncementsClient } from '@feelflow/ffid-sdk/announcements'
190
+ *
191
+ * const client = createFFIDAnnouncementsClient({
192
+ * apiBaseUrl: 'https://id.feelflow.co.jp',
193
+ * })
194
+ *
195
+ * const { data, error } = await client.listAnnouncements({ limit: 10 })
196
+ * if (data) {
197
+ * console.log(`Found ${data.total} announcements`)
198
+ * }
199
+ * ```
200
+ */
201
+
202
+ /**
203
+ * Error codes used by the Announcements SDK
204
+ */
205
+ declare const FFID_ANNOUNCEMENTS_ERROR_CODES: {
206
+ /** Network request failed (fetch threw) */
207
+ readonly NETWORK_ERROR: "NETWORK_ERROR";
208
+ /** Failed to parse server response as JSON */
209
+ readonly PARSE_ERROR: "PARSE_ERROR";
210
+ /** Server returned error without structured error body */
211
+ readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
212
+ };
213
+ /**
214
+ * Creates an FFID Announcements API client instance
215
+ *
216
+ * @param config - Client configuration (optional, uses defaults)
217
+ * @returns Announcements client instance
218
+ *
219
+ * @example
220
+ * ```typescript
221
+ * const client = createFFIDAnnouncementsClient()
222
+ * const { data, error } = await client.listAnnouncements()
223
+ * ```
224
+ */
225
+ declare function createFFIDAnnouncementsClient(config?: FFIDAnnouncementsClientConfig): {
226
+ /** List published announcements */
227
+ listAnnouncements: (options?: ListAnnouncementsOptions) => Promise<FFIDAnnouncementsApiResponse<AnnouncementListResponse>>;
228
+ /** Resolved logger instance */
229
+ logger: FFIDAnnouncementsLogger;
230
+ /** API base URL */
231
+ baseUrl: string;
232
+ };
233
+ /** Type of the FFID Announcements client */
234
+ type FFIDAnnouncementsClient = ReturnType<typeof createFFIDAnnouncementsClient>;
235
+
236
+ export { AnnouncementListResponse, DEFAULT_API_BASE_URL, FFIDAnnouncementsApiResponse, type FFIDAnnouncementsClient, FFIDAnnouncementsClientConfig, FFIDAnnouncementsLogger, FFIDConfig, FFIDError, FFIDOrganization, FFIDProvider, type FFIDProviderProps, FFIDSubscriptionContextValue, FFIDUser, FFID_ANNOUNCEMENTS_ERROR_CODES, ListAnnouncementsOptions, type UseFFIDReturn, type WithFFIDAuthOptions, type WithSubscriptionOptions, createFFIDAnnouncementsClient, useFFID, useSubscription, withFFIDAuth, withSubscription };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { useFFIDContext } from './chunk-A63MX52D.js';
2
- export { DEFAULT_API_BASE_URL, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSubscriptionBadge, FFIDUserMenu, useFFID, useSubscription, withSubscription } from './chunk-A63MX52D.js';
1
+ import { useFFIDContext } from './chunk-VXBUXOLF.js';
2
+ export { DEFAULT_API_BASE_URL, FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDProvider, FFIDSubscriptionBadge, FFIDUserMenu, FFID_ANNOUNCEMENTS_ERROR_CODES, createFFIDAnnouncementsClient, useFFID, useFFIDAnnouncements, useSubscription, withSubscription } from './chunk-VXBUXOLF.js';
3
3
  import { useRef, useEffect } from 'react';
4
4
  import { jsx, Fragment } from 'react/jsx-runtime';
5
5
 
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // src/constants.ts
4
- var DEFAULT_API_BASE_URL = "https://id.feelflow.co.jp";
3
+ var chunkP5PPUZGX_cjs = require('../chunk-P5PPUZGX.cjs');
5
4
 
6
5
  // src/legal/ffid-legal-client.ts
7
6
  var API_PREFIX = "/api/v1/legal/ext";
@@ -38,7 +37,7 @@ function createFFIDLegalClient(config) {
38
37
  if (!config.apiKey) {
39
38
  throw new Error("FFID Legal Client: apiKey \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
40
39
  }
41
- const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
40
+ const baseUrl = config.apiBaseUrl ?? chunkP5PPUZGX_cjs.DEFAULT_API_BASE_URL;
42
41
  const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
43
42
  async function fetchWithApiKey(endpoint, options = {}) {
44
43
  const url = `${baseUrl}${API_PREFIX}${endpoint}`;
@@ -175,6 +174,9 @@ function createFFIDLegalClient(config) {
175
174
  };
176
175
  }
177
176
 
178
- exports.DEFAULT_API_BASE_URL = DEFAULT_API_BASE_URL;
177
+ Object.defineProperty(exports, "DEFAULT_API_BASE_URL", {
178
+ enumerable: true,
179
+ get: function () { return chunkP5PPUZGX_cjs.DEFAULT_API_BASE_URL; }
180
+ });
179
181
  exports.FFID_LEGAL_ERROR_CODES = FFID_LEGAL_ERROR_CODES;
180
182
  exports.createFFIDLegalClient = createFFIDLegalClient;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * FFID SDK Shared Constants
3
3
  *
4
- * Constants shared across all SDK entry points (main client + legal client).
4
+ * Constants shared across all SDK entry points.
5
5
  * Centralizing these prevents drift during domain/URL changes.
6
6
  */
7
7
  /** Default FFID API base URL (production) */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * FFID SDK Shared Constants
3
3
  *
4
- * Constants shared across all SDK entry points (main client + legal client).
4
+ * Constants shared across all SDK entry points.
5
5
  * Centralizing these prevents drift during domain/URL changes.
6
6
  */
7
7
  /** Default FFID API base URL (production) */
@@ -1,5 +1,5 @@
1
- // src/constants.ts
2
- var DEFAULT_API_BASE_URL = "https://id.feelflow.co.jp";
1
+ import { DEFAULT_API_BASE_URL } from '../chunk-P4MLCG4T.js';
2
+ export { DEFAULT_API_BASE_URL } from '../chunk-P4MLCG4T.js';
3
3
 
4
4
  // src/legal/ffid-legal-client.ts
5
5
  var API_PREFIX = "/api/v1/legal/ext";
@@ -173,4 +173,4 @@ function createFFIDLegalClient(config) {
173
173
  };
174
174
  }
175
175
 
176
- export { DEFAULT_API_BASE_URL, FFID_LEGAL_ERROR_CODES, createFFIDLegalClient };
176
+ export { FFID_LEGAL_ERROR_CODES, createFFIDLegalClient };
@@ -0,0 +1,251 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ // src/webhooks/constants.ts
6
+ var FFID_WEBHOOK_SIGNATURE_HEADER = "X-FFID-Signature";
7
+ var FFID_WEBHOOK_TIMESTAMP_HEADER = "X-FFID-Timestamp";
8
+ var FFID_WEBHOOK_EVENT_ID_HEADER = "X-FFID-Event-ID";
9
+ var FFID_WEBHOOK_SIGNATURE_VERSION = "v1";
10
+ var DEFAULT_TOLERANCE_SECONDS = 300;
11
+ var MILLISECONDS_PER_SECOND = 1e3;
12
+
13
+ // src/webhooks/errors.ts
14
+ var FFIDWebhookError = class extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = "FFIDWebhookError";
18
+ }
19
+ };
20
+ var FFIDWebhookSignatureError = class extends FFIDWebhookError {
21
+ constructor(message = "Webhook signature verification failed") {
22
+ super(message);
23
+ this.name = "FFIDWebhookSignatureError";
24
+ }
25
+ };
26
+ var FFIDWebhookTimestampError = class extends FFIDWebhookError {
27
+ constructor(message = "Webhook timestamp is outside tolerance window") {
28
+ super(message);
29
+ this.name = "FFIDWebhookTimestampError";
30
+ }
31
+ };
32
+ var FFIDWebhookPayloadError = class extends FFIDWebhookError {
33
+ constructor(message = "Webhook payload is not valid JSON") {
34
+ super(message);
35
+ this.name = "FFIDWebhookPayloadError";
36
+ }
37
+ };
38
+ function parseSignatureHeader(header) {
39
+ const parts = header.split(",");
40
+ let timestamp = null;
41
+ let signature = null;
42
+ for (const part of parts) {
43
+ const [key, value] = part.split("=", 2);
44
+ if (key === "t" && value) {
45
+ timestamp = parseInt(value, 10);
46
+ } else if (key === FFID_WEBHOOK_SIGNATURE_VERSION && value) {
47
+ signature = value;
48
+ }
49
+ }
50
+ if (timestamp === null || isNaN(timestamp) || !signature) {
51
+ return null;
52
+ }
53
+ return { timestamp, signature };
54
+ }
55
+ function computeSignature(secret, timestamp, body) {
56
+ const signedContent = `${timestamp}.${body}`;
57
+ return crypto.createHmac("sha256", secret).update(signedContent).digest("hex");
58
+ }
59
+ function verifyWebhookSignature(rawBody, signatureHeader, secret, toleranceSeconds = DEFAULT_TOLERANCE_SECONDS) {
60
+ const parsed = parseSignatureHeader(signatureHeader);
61
+ if (!parsed) {
62
+ throw new FFIDWebhookSignatureError("Invalid signature header format");
63
+ }
64
+ const { timestamp, signature } = parsed;
65
+ const now = Math.floor(Date.now() / MILLISECONDS_PER_SECOND);
66
+ if (Math.abs(now - timestamp) > toleranceSeconds) {
67
+ throw new FFIDWebhookTimestampError(
68
+ `Webhook timestamp is outside tolerance window (${toleranceSeconds}s)`
69
+ );
70
+ }
71
+ const expected = computeSignature(secret, timestamp, rawBody);
72
+ const HEX_REGEX = /^[0-9a-f]+$/i;
73
+ if (!HEX_REGEX.test(signature)) {
74
+ throw new FFIDWebhookSignatureError("Webhook signature verification failed");
75
+ }
76
+ const sigBuffer = Buffer.from(signature, "hex");
77
+ const expectedBuffer = Buffer.from(expected, "hex");
78
+ if (sigBuffer.length !== expectedBuffer.length) {
79
+ throw new FFIDWebhookSignatureError("Webhook signature verification failed");
80
+ }
81
+ if (!crypto.timingSafeEqual(sigBuffer, expectedBuffer)) {
82
+ throw new FFIDWebhookSignatureError("Webhook signature verification failed");
83
+ }
84
+ try {
85
+ return JSON.parse(rawBody);
86
+ } catch {
87
+ throw new FFIDWebhookPayloadError("Webhook payload is not valid JSON");
88
+ }
89
+ }
90
+
91
+ // src/webhooks/handler.ts
92
+ var SDK_LOG_PREFIX = "[FFID Webhook SDK]";
93
+ var HTTP_OK = 200;
94
+ var HTTP_BAD_REQUEST = 400;
95
+ var noopLogger = {
96
+ debug: () => {
97
+ },
98
+ info: () => {
99
+ },
100
+ warn: () => {
101
+ },
102
+ error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
103
+ };
104
+ function createFFIDWebhookHandler(config) {
105
+ if (!config.secret) {
106
+ throw new Error("FFID Webhook Handler: secret is required");
107
+ }
108
+ const logger = config.logger ?? noopLogger;
109
+ const toleranceSeconds = config.toleranceSeconds;
110
+ const handlers = /* @__PURE__ */ new Map();
111
+ const allHandlers = /* @__PURE__ */ new Set();
112
+ function on(eventType, handler) {
113
+ if (!handlers.has(eventType)) {
114
+ handlers.set(eventType, /* @__PURE__ */ new Set());
115
+ }
116
+ handlers.get(eventType).add(handler);
117
+ logger.debug(`Registered handler for ${eventType}`);
118
+ }
119
+ function onAll(handler) {
120
+ allHandlers.add(handler);
121
+ logger.debug("Registered catch-all handler");
122
+ }
123
+ function off(eventType, handler) {
124
+ const set = handlers.get(eventType);
125
+ if (set) {
126
+ set.delete(handler);
127
+ if (set.size === 0) {
128
+ handlers.delete(eventType);
129
+ }
130
+ }
131
+ }
132
+ async function dispatchEvent(event) {
133
+ const typeHandlers = handlers.get(event.type);
134
+ const promises = [];
135
+ if (typeHandlers) {
136
+ for (const handler of typeHandlers) {
137
+ promises.push(Promise.resolve(handler(event)));
138
+ }
139
+ }
140
+ for (const handler of allHandlers) {
141
+ promises.push(Promise.resolve(handler(event)));
142
+ }
143
+ await Promise.all(promises);
144
+ }
145
+ async function handleEvent(rawBody, signatureHeader) {
146
+ const event = verifyWebhookSignature(
147
+ rawBody,
148
+ signatureHeader,
149
+ config.secret,
150
+ toleranceSeconds
151
+ );
152
+ logger.info(`Received event: ${event.type} (${event.id})`);
153
+ await dispatchEvent(event);
154
+ return event;
155
+ }
156
+ async function handleRaw(rawBody, headers) {
157
+ const headerLower = FFID_WEBHOOK_SIGNATURE_HEADER.toLowerCase();
158
+ let signatureHeader;
159
+ for (const [key, value] of Object.entries(headers)) {
160
+ if (key.toLowerCase() === headerLower) {
161
+ signatureHeader = value;
162
+ break;
163
+ }
164
+ }
165
+ if (!signatureHeader) {
166
+ throw new FFIDWebhookError(
167
+ `Missing ${FFID_WEBHOOK_SIGNATURE_HEADER} header`
168
+ );
169
+ }
170
+ return handleEvent(rawBody, signatureHeader);
171
+ }
172
+ function expressMiddleware() {
173
+ return (req, res, _next) => {
174
+ const rawBody = typeof req.body === "string" ? req.body : Buffer.isBuffer(req.body) ? req.body.toString("utf-8") : JSON.stringify(req.body);
175
+ const signatureHeader = req.headers[FFID_WEBHOOK_SIGNATURE_HEADER.toLowerCase()];
176
+ if (!signatureHeader) {
177
+ res.status(HTTP_BAD_REQUEST).json({
178
+ error: `Missing ${FFID_WEBHOOK_SIGNATURE_HEADER} header`
179
+ });
180
+ return;
181
+ }
182
+ handleEvent(rawBody, signatureHeader).then((event) => {
183
+ res.status(HTTP_OK).json({ received: true, eventId: event.id });
184
+ }).catch((error) => {
185
+ logger.error("Webhook processing failed:", error);
186
+ const message = error instanceof Error ? error.message : "Webhook processing failed";
187
+ res.status(HTTP_BAD_REQUEST).json({ error: message });
188
+ });
189
+ };
190
+ }
191
+ function nextHandler() {
192
+ return async (request) => {
193
+ try {
194
+ const rawBody = await request.text();
195
+ const signatureHeader = request.headers.get(
196
+ FFID_WEBHOOK_SIGNATURE_HEADER
197
+ );
198
+ if (!signatureHeader) {
199
+ return new Response(
200
+ JSON.stringify({
201
+ error: `Missing ${FFID_WEBHOOK_SIGNATURE_HEADER} header`
202
+ }),
203
+ { status: HTTP_BAD_REQUEST, headers: { "Content-Type": "application/json" } }
204
+ );
205
+ }
206
+ const event = await handleEvent(rawBody, signatureHeader);
207
+ return new Response(
208
+ JSON.stringify({ received: true, eventId: event.id }),
209
+ { status: HTTP_OK, headers: { "Content-Type": "application/json" } }
210
+ );
211
+ } catch (error) {
212
+ logger.error("Webhook processing failed:", error);
213
+ const message = error instanceof Error ? error.message : "Webhook processing failed";
214
+ return new Response(
215
+ JSON.stringify({ error: message }),
216
+ { status: HTTP_BAD_REQUEST, headers: { "Content-Type": "application/json" } }
217
+ );
218
+ }
219
+ };
220
+ }
221
+ return {
222
+ /** Register a handler for a specific event type */
223
+ on,
224
+ /** Register a handler that receives all events */
225
+ onAll,
226
+ /** Unregister a handler for a specific event type */
227
+ off,
228
+ /** Verify signature and dispatch event (core method) */
229
+ handleEvent,
230
+ /** Process webhook from raw body + headers object */
231
+ handleRaw,
232
+ /** Express/Connect middleware */
233
+ expressMiddleware,
234
+ /** Next.js App Router route handler */
235
+ nextHandler
236
+ };
237
+ }
238
+
239
+ exports.DEFAULT_TOLERANCE_SECONDS = DEFAULT_TOLERANCE_SECONDS;
240
+ exports.FFIDWebhookError = FFIDWebhookError;
241
+ exports.FFIDWebhookPayloadError = FFIDWebhookPayloadError;
242
+ exports.FFIDWebhookSignatureError = FFIDWebhookSignatureError;
243
+ exports.FFIDWebhookTimestampError = FFIDWebhookTimestampError;
244
+ exports.FFID_WEBHOOK_EVENT_ID_HEADER = FFID_WEBHOOK_EVENT_ID_HEADER;
245
+ exports.FFID_WEBHOOK_SIGNATURE_HEADER = FFID_WEBHOOK_SIGNATURE_HEADER;
246
+ exports.FFID_WEBHOOK_SIGNATURE_VERSION = FFID_WEBHOOK_SIGNATURE_VERSION;
247
+ exports.FFID_WEBHOOK_TIMESTAMP_HEADER = FFID_WEBHOOK_TIMESTAMP_HEADER;
248
+ exports.computeSignature = computeSignature;
249
+ exports.createFFIDWebhookHandler = createFFIDWebhookHandler;
250
+ exports.parseSignatureHeader = parseSignatureHeader;
251
+ exports.verifyWebhookSignature = verifyWebhookSignature;