@skillrecordings/sdk 0.4.0 → 0.5.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/client.d.ts +13 -1
- package/dist/client.js +69 -0
- package/dist/client.js.map +1 -1
- package/dist/handler.js +75 -0
- package/dist/handler.js.map +1 -1
- package/dist/integration.d.ts +79 -2
- package/dist/types.d.ts +277 -1
- package/dist/types.js +107 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SupportIntegration } from './integration.js';
|
|
2
|
-
import { User, Purchase, Subscription, ActionResult, ClaimedSeat, ContentSearchRequest, ContentSearchResponse, ProductStatus } from './types.js';
|
|
2
|
+
import { User, Purchase, Subscription, ActionResult, ClaimedSeat, ContentSearchRequest, ContentSearchResponse, ProductStatus, Promotion, CouponInfo, RefundPolicy, ContentAccess, UserActivity, LicenseInfo, AppInfo } from './types.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -39,6 +39,11 @@ declare class IntegrationClient implements SupportIntegration {
|
|
|
39
39
|
* Uses action-based routing: all requests go to /api/support with action in body.
|
|
40
40
|
*/
|
|
41
41
|
private request;
|
|
42
|
+
/**
|
|
43
|
+
* Make a signed request for an optional method.
|
|
44
|
+
* Returns the fallback value if the app responds with 501 (Not Implemented).
|
|
45
|
+
*/
|
|
46
|
+
private requestOptional;
|
|
42
47
|
lookupUser(email: string): Promise<User | null>;
|
|
43
48
|
getPurchases(userId: string): Promise<Purchase[]>;
|
|
44
49
|
getSubscriptions(userId: string): Promise<Subscription[]>;
|
|
@@ -69,6 +74,13 @@ declare class IntegrationClient implements SupportIntegration {
|
|
|
69
74
|
getClaimedSeats(bulkCouponId: string): Promise<ClaimedSeat[]>;
|
|
70
75
|
searchContent(request: ContentSearchRequest): Promise<ContentSearchResponse>;
|
|
71
76
|
getProductStatus(productId: string): Promise<ProductStatus | null>;
|
|
77
|
+
getActivePromotions(): Promise<Promotion[]>;
|
|
78
|
+
getCouponInfo(code: string): Promise<CouponInfo | null>;
|
|
79
|
+
getRefundPolicy(): Promise<RefundPolicy>;
|
|
80
|
+
getContentAccess(userId: string): Promise<ContentAccess>;
|
|
81
|
+
getRecentActivity(userId: string): Promise<UserActivity>;
|
|
82
|
+
getLicenseInfo(purchaseId: string): Promise<LicenseInfo | null>;
|
|
83
|
+
getAppInfo(): Promise<AppInfo>;
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
export { IntegrationClient };
|
package/dist/client.js
CHANGED
|
@@ -54,6 +54,42 @@ var IntegrationClient = class {
|
|
|
54
54
|
}
|
|
55
55
|
return await response.json();
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Make a signed request for an optional method.
|
|
59
|
+
* Returns the fallback value if the app responds with 501 (Not Implemented).
|
|
60
|
+
*/
|
|
61
|
+
async requestOptional(action, payload, fallback) {
|
|
62
|
+
const body = JSON.stringify({ action, ...payload });
|
|
63
|
+
const signature = this.generateSignature(body);
|
|
64
|
+
const response = await fetch(this.baseUrl, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
"X-Support-Signature": signature
|
|
69
|
+
},
|
|
70
|
+
body
|
|
71
|
+
});
|
|
72
|
+
if (response.status === 501) {
|
|
73
|
+
return fallback;
|
|
74
|
+
}
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
let errorMessage;
|
|
77
|
+
try {
|
|
78
|
+
const errorBody = await response.json();
|
|
79
|
+
if (errorBody?.error) {
|
|
80
|
+
errorMessage = errorBody.error;
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
if (errorMessage) {
|
|
85
|
+
throw new Error(errorMessage);
|
|
86
|
+
}
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Integration request failed: ${response.status} ${response.statusText}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return await response.json();
|
|
92
|
+
}
|
|
57
93
|
async lookupUser(email) {
|
|
58
94
|
return this.request("lookupUser", { email });
|
|
59
95
|
}
|
|
@@ -90,6 +126,39 @@ var IntegrationClient = class {
|
|
|
90
126
|
async getProductStatus(productId) {
|
|
91
127
|
return this.request("getProductStatus", { productId });
|
|
92
128
|
}
|
|
129
|
+
// ── Agent Intelligence Methods ─────────────────────────────────────
|
|
130
|
+
// These methods handle 501 (Not Implemented) gracefully by returning
|
|
131
|
+
// null or empty arrays, allowing the agent to function even when
|
|
132
|
+
// the app hasn't implemented optional methods.
|
|
133
|
+
async getActivePromotions() {
|
|
134
|
+
return this.requestOptional("getActivePromotions", {}, []);
|
|
135
|
+
}
|
|
136
|
+
async getCouponInfo(code) {
|
|
137
|
+
return this.requestOptional(
|
|
138
|
+
"getCouponInfo",
|
|
139
|
+
{ code },
|
|
140
|
+
null
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
async getRefundPolicy() {
|
|
144
|
+
return this.request("getRefundPolicy", {});
|
|
145
|
+
}
|
|
146
|
+
async getContentAccess(userId) {
|
|
147
|
+
return this.request("getContentAccess", { userId });
|
|
148
|
+
}
|
|
149
|
+
async getRecentActivity(userId) {
|
|
150
|
+
return this.request("getRecentActivity", { userId });
|
|
151
|
+
}
|
|
152
|
+
async getLicenseInfo(purchaseId) {
|
|
153
|
+
return this.requestOptional(
|
|
154
|
+
"getLicenseInfo",
|
|
155
|
+
{ purchaseId },
|
|
156
|
+
null
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
async getAppInfo() {
|
|
160
|
+
return this.request("getAppInfo", {});
|
|
161
|
+
}
|
|
93
162
|
};
|
|
94
163
|
export {
|
|
95
164
|
IntegrationClient
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { createHmac } from 'node:crypto'\nimport type {\n ActionResult,\n ClaimedSeat,\n ContentSearchRequest,\n ContentSearchResponse,\n ProductStatus,\n Purchase,\n Subscription,\n SupportIntegration,\n User,\n} from './integration'\n\n/**\n * Client for calling app integration endpoints with HMAC-signed requests.\n *\n * Used by core to call app-specific support actions (lookupUser, getPurchases, etc.)\n * with Stripe-style HMAC-SHA256 signature verification.\n *\n * @example\n * ```typescript\n * import { IntegrationClient } from '@skillrecordings/sdk/client'\n *\n * const client = new IntegrationClient({\n * baseUrl: 'https://totaltypescript.com',\n * webhookSecret: 'whsec_abc123',\n * })\n *\n * const user = await client.lookupUser('test@example.com')\n * ```\n */\nexport class IntegrationClient implements SupportIntegration {\n private readonly baseUrl: string\n private readonly webhookSecret: string\n\n constructor(config: { baseUrl: string; webhookSecret: string }) {\n // Strip trailing slash for consistent URL construction\n this.baseUrl = config.baseUrl.replace(/\\/$/, '')\n this.webhookSecret = config.webhookSecret\n }\n\n /**\n * Generate HMAC-SHA256 signature for request body.\n * Format: `timestamp=<timestamp>,v1=<signature>`\n *\n * Signature is computed as: HMAC-SHA256(timestamp + \".\" + body, secret)\n */\n private generateSignature(body: string): string {\n const timestamp = Math.floor(Date.now() / 1000)\n const signedPayload = `${timestamp}.${body}`\n const signature = createHmac('sha256', this.webhookSecret)\n .update(signedPayload)\n .digest('hex')\n\n return `timestamp=${timestamp},v1=${signature}`\n }\n\n /**\n * Make signed POST request to app integration endpoint.\n * Uses action-based routing: all requests go to /api/support with action in body.\n */\n private async request<T>(\n action: string,\n payload: Record<string, unknown>\n ): Promise<T> {\n const body = JSON.stringify({ action, ...payload })\n const signature = this.generateSignature(body)\n\n // baseUrl should be the complete endpoint URL (e.g., https://example.com/api/support)\n const response = await fetch(this.baseUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Support-Signature': signature,\n },\n body,\n })\n\n if (!response.ok) {\n // Try to extract error message from response body\n let errorMessage: string | undefined\n try {\n const errorBody = (await response.json()) as { error?: string }\n if (errorBody?.error) {\n errorMessage = errorBody.error\n }\n } catch {\n // If JSON parsing fails, ignore and use status text\n }\n\n if (errorMessage) {\n throw new Error(errorMessage)\n }\n throw new Error(\n `Integration request failed: ${response.status} ${response.statusText}`\n )\n }\n\n return (await response.json()) as T\n }\n\n async lookupUser(email: string): Promise<User | null> {\n return this.request('lookupUser', { email })\n }\n\n async getPurchases(userId: string): Promise<Purchase[]> {\n return this.request('getPurchases', { userId })\n }\n\n async getSubscriptions(userId: string): Promise<Subscription[]> {\n return this.request('getSubscriptions', { userId })\n }\n\n async revokeAccess(params: {\n purchaseId: string\n reason: string\n refundId: string\n }): Promise<ActionResult> {\n return this.request('revokeAccess', params)\n }\n\n async transferPurchase(params: {\n purchaseId: string\n fromUserId: string\n toEmail: string\n }): Promise<ActionResult> {\n return this.request('transferPurchase', params)\n }\n\n async generateMagicLink(params: {\n email: string\n expiresIn: number\n }): Promise<{ url: string }> {\n return this.request('generateMagicLink', params)\n }\n\n async updateEmail(params: {\n userId: string\n newEmail: string\n }): Promise<ActionResult> {\n return this.request('updateEmail', params)\n }\n\n async updateName(params: {\n userId: string\n newName: string\n }): Promise<ActionResult> {\n return this.request('updateName', params)\n }\n\n async getClaimedSeats(bulkCouponId: string): Promise<ClaimedSeat[]> {\n return this.request('getClaimedSeats', { bulkCouponId })\n }\n\n async searchContent(\n request: ContentSearchRequest\n ): Promise<ContentSearchResponse> {\n return this.request(\n 'searchContent',\n request as unknown as Record<string, unknown>\n )\n }\n\n async getProductStatus(productId: string): Promise<ProductStatus | null> {\n return this.request('getProductStatus', { productId })\n }\n}\n"],"mappings":";;;AAAA,SAAS,kBAAkB;AA+BpB,IAAM,oBAAN,MAAsD;AAAA,EAC1C;AAAA,EACA;AAAA,EAEjB,YAAY,QAAoD;AAE9D,SAAK,UAAU,OAAO,QAAQ,QAAQ,OAAO,EAAE;AAC/C,SAAK,gBAAgB,OAAO;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,kBAAkB,MAAsB;AAC9C,UAAM,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC9C,UAAM,gBAAgB,GAAG,SAAS,IAAI,IAAI;AAC1C,UAAM,YAAY,WAAW,UAAU,KAAK,aAAa,EACtD,OAAO,aAAa,EACpB,OAAO,KAAK;AAEf,WAAO,aAAa,SAAS,OAAO,SAAS;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,QACZ,QACA,SACY;AACZ,UAAM,OAAO,KAAK,UAAU,EAAE,QAAQ,GAAG,QAAQ,CAAC;AAClD,UAAM,YAAY,KAAK,kBAAkB,IAAI;AAG7C,UAAM,WAAW,MAAM,MAAM,KAAK,SAAS;AAAA,MACzC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,uBAAuB;AAAA,MACzB;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAEhB,UAAI;AACJ,UAAI;AACF,cAAM,YAAa,MAAM,SAAS,KAAK;AACvC,YAAI,WAAW,OAAO;AACpB,yBAAe,UAAU;AAAA,QAC3B;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,UAAI,cAAc;AAChB,cAAM,IAAI,MAAM,YAAY;AAAA,MAC9B;AACA,YAAM,IAAI;AAAA,QACR,+BAA+B,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,MACvE;AAAA,IACF;AAEA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC9B;AAAA,EAEA,MAAM,WAAW,OAAqC;AACpD,WAAO,KAAK,QAAQ,cAAc,EAAE,MAAM,CAAC;AAAA,EAC7C;AAAA,EAEA,MAAM,aAAa,QAAqC;AACtD,WAAO,KAAK,QAAQ,gBAAgB,EAAE,OAAO,CAAC;AAAA,EAChD;AAAA,EAEA,MAAM,iBAAiB,QAAyC;AAC9D,WAAO,KAAK,QAAQ,oBAAoB,EAAE,OAAO,CAAC;AAAA,EACpD;AAAA,EAEA,MAAM,aAAa,QAIO;AACxB,WAAO,KAAK,QAAQ,gBAAgB,MAAM;AAAA,EAC5C;AAAA,EAEA,MAAM,iBAAiB,QAIG;AACxB,WAAO,KAAK,QAAQ,oBAAoB,MAAM;AAAA,EAChD;AAAA,EAEA,MAAM,kBAAkB,QAGK;AAC3B,WAAO,KAAK,QAAQ,qBAAqB,MAAM;AAAA,EACjD;AAAA,EAEA,MAAM,YAAY,QAGQ;AACxB,WAAO,KAAK,QAAQ,eAAe,MAAM;AAAA,EAC3C;AAAA,EAEA,MAAM,WAAW,QAGS;AACxB,WAAO,KAAK,QAAQ,cAAc,MAAM;AAAA,EAC1C;AAAA,EAEA,MAAM,gBAAgB,cAA8C;AAClE,WAAO,KAAK,QAAQ,mBAAmB,EAAE,aAAa,CAAC;AAAA,EACzD;AAAA,EAEA,MAAM,cACJ,SACgC;AAChC,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,iBAAiB,WAAkD;AACvE,WAAO,KAAK,QAAQ,oBAAoB,EAAE,UAAU,CAAC;AAAA,EACvD;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { createHmac } from 'node:crypto'\nimport type {\n ActionResult,\n AppInfo,\n ClaimedSeat,\n ContentAccess,\n ContentSearchRequest,\n ContentSearchResponse,\n CouponInfo,\n LicenseInfo,\n ProductStatus,\n Promotion,\n Purchase,\n RefundPolicy,\n Subscription,\n SupportIntegration,\n User,\n UserActivity,\n} from './integration'\n\n/**\n * Client for calling app integration endpoints with HMAC-signed requests.\n *\n * Used by core to call app-specific support actions (lookupUser, getPurchases, etc.)\n * with Stripe-style HMAC-SHA256 signature verification.\n *\n * @example\n * ```typescript\n * import { IntegrationClient } from '@skillrecordings/sdk/client'\n *\n * const client = new IntegrationClient({\n * baseUrl: 'https://totaltypescript.com',\n * webhookSecret: 'whsec_abc123',\n * })\n *\n * const user = await client.lookupUser('test@example.com')\n * ```\n */\nexport class IntegrationClient implements SupportIntegration {\n private readonly baseUrl: string\n private readonly webhookSecret: string\n\n constructor(config: { baseUrl: string; webhookSecret: string }) {\n // Strip trailing slash for consistent URL construction\n this.baseUrl = config.baseUrl.replace(/\\/$/, '')\n this.webhookSecret = config.webhookSecret\n }\n\n /**\n * Generate HMAC-SHA256 signature for request body.\n * Format: `timestamp=<timestamp>,v1=<signature>`\n *\n * Signature is computed as: HMAC-SHA256(timestamp + \".\" + body, secret)\n */\n private generateSignature(body: string): string {\n const timestamp = Math.floor(Date.now() / 1000)\n const signedPayload = `${timestamp}.${body}`\n const signature = createHmac('sha256', this.webhookSecret)\n .update(signedPayload)\n .digest('hex')\n\n return `timestamp=${timestamp},v1=${signature}`\n }\n\n /**\n * Make signed POST request to app integration endpoint.\n * Uses action-based routing: all requests go to /api/support with action in body.\n */\n private async request<T>(\n action: string,\n payload: Record<string, unknown>\n ): Promise<T> {\n const body = JSON.stringify({ action, ...payload })\n const signature = this.generateSignature(body)\n\n // baseUrl should be the complete endpoint URL (e.g., https://example.com/api/support)\n const response = await fetch(this.baseUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Support-Signature': signature,\n },\n body,\n })\n\n if (!response.ok) {\n // Try to extract error message from response body\n let errorMessage: string | undefined\n try {\n const errorBody = (await response.json()) as { error?: string }\n if (errorBody?.error) {\n errorMessage = errorBody.error\n }\n } catch {\n // If JSON parsing fails, ignore and use status text\n }\n\n if (errorMessage) {\n throw new Error(errorMessage)\n }\n throw new Error(\n `Integration request failed: ${response.status} ${response.statusText}`\n )\n }\n\n return (await response.json()) as T\n }\n\n /**\n * Make a signed request for an optional method.\n * Returns the fallback value if the app responds with 501 (Not Implemented).\n */\n private async requestOptional<T>(\n action: string,\n payload: Record<string, unknown>,\n fallback: T\n ): Promise<T> {\n const body = JSON.stringify({ action, ...payload })\n const signature = this.generateSignature(body)\n\n const response = await fetch(this.baseUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Support-Signature': signature,\n },\n body,\n })\n\n // 501 = method not implemented by this app — return fallback gracefully\n if (response.status === 501) {\n return fallback\n }\n\n if (!response.ok) {\n let errorMessage: string | undefined\n try {\n const errorBody = (await response.json()) as { error?: string }\n if (errorBody?.error) {\n errorMessage = errorBody.error\n }\n } catch {\n // If JSON parsing fails, ignore and use status text\n }\n\n if (errorMessage) {\n throw new Error(errorMessage)\n }\n throw new Error(\n `Integration request failed: ${response.status} ${response.statusText}`\n )\n }\n\n return (await response.json()) as T\n }\n\n async lookupUser(email: string): Promise<User | null> {\n return this.request('lookupUser', { email })\n }\n\n async getPurchases(userId: string): Promise<Purchase[]> {\n return this.request('getPurchases', { userId })\n }\n\n async getSubscriptions(userId: string): Promise<Subscription[]> {\n return this.request('getSubscriptions', { userId })\n }\n\n async revokeAccess(params: {\n purchaseId: string\n reason: string\n refundId: string\n }): Promise<ActionResult> {\n return this.request('revokeAccess', params)\n }\n\n async transferPurchase(params: {\n purchaseId: string\n fromUserId: string\n toEmail: string\n }): Promise<ActionResult> {\n return this.request('transferPurchase', params)\n }\n\n async generateMagicLink(params: {\n email: string\n expiresIn: number\n }): Promise<{ url: string }> {\n return this.request('generateMagicLink', params)\n }\n\n async updateEmail(params: {\n userId: string\n newEmail: string\n }): Promise<ActionResult> {\n return this.request('updateEmail', params)\n }\n\n async updateName(params: {\n userId: string\n newName: string\n }): Promise<ActionResult> {\n return this.request('updateName', params)\n }\n\n async getClaimedSeats(bulkCouponId: string): Promise<ClaimedSeat[]> {\n return this.request('getClaimedSeats', { bulkCouponId })\n }\n\n async searchContent(\n request: ContentSearchRequest\n ): Promise<ContentSearchResponse> {\n return this.request(\n 'searchContent',\n request as unknown as Record<string, unknown>\n )\n }\n\n async getProductStatus(productId: string): Promise<ProductStatus | null> {\n return this.request('getProductStatus', { productId })\n }\n\n // ── Agent Intelligence Methods ─────────────────────────────────────\n // These methods handle 501 (Not Implemented) gracefully by returning\n // null or empty arrays, allowing the agent to function even when\n // the app hasn't implemented optional methods.\n\n async getActivePromotions(): Promise<Promotion[]> {\n return this.requestOptional<Promotion[]>('getActivePromotions', {}, [])\n }\n\n async getCouponInfo(code: string): Promise<CouponInfo | null> {\n return this.requestOptional<CouponInfo | null>(\n 'getCouponInfo',\n { code },\n null\n )\n }\n\n async getRefundPolicy(): Promise<RefundPolicy> {\n return this.request('getRefundPolicy', {})\n }\n\n async getContentAccess(userId: string): Promise<ContentAccess> {\n return this.request('getContentAccess', { userId })\n }\n\n async getRecentActivity(userId: string): Promise<UserActivity> {\n return this.request('getRecentActivity', { userId })\n }\n\n async getLicenseInfo(purchaseId: string): Promise<LicenseInfo | null> {\n return this.requestOptional<LicenseInfo | null>(\n 'getLicenseInfo',\n { purchaseId },\n null\n )\n }\n\n async getAppInfo(): Promise<AppInfo> {\n return this.request('getAppInfo', {})\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/handler.js
CHANGED
|
@@ -168,6 +168,81 @@ async function routeAction(integration, action, body) {
|
|
|
168
168
|
const result = await integration.getProductStatus(productId);
|
|
169
169
|
return { data: result, status: 200 };
|
|
170
170
|
}
|
|
171
|
+
// ── Agent Intelligence Methods ─────────────────────────────────
|
|
172
|
+
case "getActivePromotions": {
|
|
173
|
+
if (!integration.getActivePromotions) {
|
|
174
|
+
return {
|
|
175
|
+
data: { error: "Method not implemented: getActivePromotions" },
|
|
176
|
+
status: 501
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const result = await integration.getActivePromotions();
|
|
180
|
+
return { data: result, status: 200 };
|
|
181
|
+
}
|
|
182
|
+
case "getCouponInfo": {
|
|
183
|
+
if (!integration.getCouponInfo) {
|
|
184
|
+
return {
|
|
185
|
+
data: { error: "Method not implemented: getCouponInfo" },
|
|
186
|
+
status: 501
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const code = body.code;
|
|
190
|
+
const result = await integration.getCouponInfo(code);
|
|
191
|
+
return { data: result, status: 200 };
|
|
192
|
+
}
|
|
193
|
+
case "getRefundPolicy": {
|
|
194
|
+
if (!integration.getRefundPolicy) {
|
|
195
|
+
return {
|
|
196
|
+
data: { error: "Method not implemented: getRefundPolicy" },
|
|
197
|
+
status: 501
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const result = await integration.getRefundPolicy();
|
|
201
|
+
return { data: result, status: 200 };
|
|
202
|
+
}
|
|
203
|
+
case "getContentAccess": {
|
|
204
|
+
if (!integration.getContentAccess) {
|
|
205
|
+
return {
|
|
206
|
+
data: { error: "Method not implemented: getContentAccess" },
|
|
207
|
+
status: 501
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const userId = body.userId;
|
|
211
|
+
const result = await integration.getContentAccess(userId);
|
|
212
|
+
return { data: result, status: 200 };
|
|
213
|
+
}
|
|
214
|
+
case "getRecentActivity": {
|
|
215
|
+
if (!integration.getRecentActivity) {
|
|
216
|
+
return {
|
|
217
|
+
data: { error: "Method not implemented: getRecentActivity" },
|
|
218
|
+
status: 501
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const userId = body.userId;
|
|
222
|
+
const result = await integration.getRecentActivity(userId);
|
|
223
|
+
return { data: result, status: 200 };
|
|
224
|
+
}
|
|
225
|
+
case "getLicenseInfo": {
|
|
226
|
+
if (!integration.getLicenseInfo) {
|
|
227
|
+
return {
|
|
228
|
+
data: { error: "Method not implemented: getLicenseInfo" },
|
|
229
|
+
status: 501
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const purchaseId = body.purchaseId;
|
|
233
|
+
const result = await integration.getLicenseInfo(purchaseId);
|
|
234
|
+
return { data: result, status: 200 };
|
|
235
|
+
}
|
|
236
|
+
case "getAppInfo": {
|
|
237
|
+
if (!integration.getAppInfo) {
|
|
238
|
+
return {
|
|
239
|
+
data: { error: "Method not implemented: getAppInfo" },
|
|
240
|
+
status: 501
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const result = await integration.getAppInfo();
|
|
244
|
+
return { data: result, status: 200 };
|
|
245
|
+
}
|
|
171
246
|
default:
|
|
172
247
|
return {
|
|
173
248
|
data: { error: `Unknown action: ${action}` },
|
package/dist/handler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/handler.ts"],"sourcesContent":["import { timingSafeEqual } from 'crypto'\nimport type { SupportIntegration } from './integration'\nimport type { ContentSearchRequest, ContentSearchResponse } from './types'\n\n/**\n * Configuration for createSupportHandler\n */\nexport interface SupportHandlerConfig {\n integration: SupportIntegration\n webhookSecret: string\n}\n\n/**\n * Request body for webhook actions\n */\ninterface WebhookRequest {\n action: string\n [key: string]: unknown\n}\n\n/**\n * Creates a Next.js API route handler for SupportIntegration.\n * Verifies HMAC-SHA256 signature and routes actions to integration methods.\n *\n * Signature format: timestamp=1234567890,v1=hex_signature\n * Payload to sign: timestamp.JSON.stringify(body)\n * Replay protection: 5 minute window\n *\n * @example\n * ```typescript\n * import { createSupportHandler } from '@skillrecordings/sdk/handler'\n * import { integration } from './integration'\n *\n * export const POST = createSupportHandler({\n * integration,\n * webhookSecret: process.env.SUPPORT_WEBHOOK_SECRET!,\n * })\n * ```\n */\nexport function createSupportHandler(\n config: SupportHandlerConfig\n): (request: Request) => Promise<Response> {\n const { integration, webhookSecret } = config\n\n return async function handler(request: Request): Promise<Response> {\n try {\n // 1. Extract signature header\n const signatureHeader = request.headers.get('x-support-signature')\n if (!signatureHeader) {\n return jsonResponse({ error: 'Missing signature header' }, 401)\n }\n\n // 2. Parse signature header (format: timestamp=1234567890,v1=hex_signature)\n const parts = signatureHeader.split(',')\n const timestampPart = parts.find((p) => p.startsWith('timestamp='))\n const signaturePart = parts.find((p) => p.startsWith('v1='))\n\n if (!timestampPart || !signaturePart) {\n return jsonResponse({ error: 'Invalid signature format' }, 401)\n }\n\n const timestampValue = timestampPart.split('=')[1]\n const signatureValue = signaturePart.split('=')[1]\n\n if (!timestampValue || !signatureValue) {\n return jsonResponse({ error: 'Invalid signature format' }, 401)\n }\n\n const timestamp = parseInt(timestampValue, 10)\n const receivedSignature = signatureValue\n\n // 3. Verify timestamp (replay protection - 5 minute window)\n const now = Math.floor(Date.now() / 1000)\n const maxAge = 300 // 5 minutes in seconds\n if (now - timestamp > maxAge) {\n return jsonResponse({ error: 'Signature expired' }, 401)\n }\n\n // 4. Read and parse body\n const bodyText = await request.text()\n let body: WebhookRequest\n\n try {\n body = JSON.parse(bodyText)\n } catch (err) {\n return jsonResponse({ error: 'Invalid JSON body' }, 400)\n }\n\n // 5. Compute expected signature\n const crypto = await import('crypto')\n const payload = `${timestamp}.${bodyText}`\n const expectedSignature = crypto\n .createHmac('sha256', webhookSecret)\n .update(payload)\n .digest('hex')\n\n // 6. Timing-safe comparison to prevent timing attacks\n if (\n !timingSafeEqual(\n Buffer.from(receivedSignature),\n Buffer.from(expectedSignature)\n )\n ) {\n return jsonResponse({ error: 'Invalid signature' }, 401)\n }\n\n // 7. Extract action field\n const { action } = body\n if (!action || typeof action !== 'string') {\n return jsonResponse({ error: 'Missing action field' }, 400)\n }\n\n // 8. Route to integration method\n const result = await routeAction(integration, action, body)\n return jsonResponse(result.data, result.status)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n return jsonResponse({ error: `Internal error: ${message}` }, 500)\n }\n }\n}\n\n/**\n * Routes action to appropriate integration method\n */\nasync function routeAction(\n integration: SupportIntegration,\n action: string,\n body: WebhookRequest\n): Promise<{ data: unknown; status: number }> {\n try {\n switch (action) {\n case 'lookupUser': {\n const email = (body as unknown as { email: string }).email\n const result = await integration.lookupUser(email)\n return { data: result, status: 200 }\n }\n\n case 'getPurchases': {\n const userId = (body as unknown as { userId: string }).userId\n const result = await integration.getPurchases(userId)\n return { data: result, status: 200 }\n }\n\n case 'revokeAccess': {\n const params = body as unknown as {\n purchaseId: string\n reason: string\n refundId: string\n }\n const result = await integration.revokeAccess({\n purchaseId: params.purchaseId,\n reason: params.reason,\n refundId: params.refundId,\n })\n return { data: result, status: 200 }\n }\n\n case 'transferPurchase': {\n const params = body as unknown as {\n purchaseId: string\n fromUserId: string\n toEmail: string\n }\n const result = await integration.transferPurchase({\n purchaseId: params.purchaseId,\n fromUserId: params.fromUserId,\n toEmail: params.toEmail,\n })\n return { data: result, status: 200 }\n }\n\n case 'generateMagicLink': {\n const params = body as unknown as {\n email: string\n expiresIn: number\n }\n const result = await integration.generateMagicLink({\n email: params.email,\n expiresIn: params.expiresIn,\n })\n return { data: result, status: 200 }\n }\n\n // Optional methods\n case 'getSubscriptions': {\n if (!integration.getSubscriptions) {\n return {\n data: { error: 'Method not implemented: getSubscriptions' },\n status: 501,\n }\n }\n const userId = (body as unknown as { userId: string }).userId\n const result = await integration.getSubscriptions(userId)\n return { data: result, status: 200 }\n }\n\n case 'updateEmail': {\n if (!integration.updateEmail) {\n return {\n data: { error: 'Method not implemented: updateEmail' },\n status: 501,\n }\n }\n const params = body as unknown as {\n userId: string\n newEmail: string\n }\n const result = await integration.updateEmail({\n userId: params.userId,\n newEmail: params.newEmail,\n })\n return { data: result, status: 200 }\n }\n\n case 'updateName': {\n if (!integration.updateName) {\n return {\n data: { error: 'Method not implemented: updateName' },\n status: 501,\n }\n }\n const params = body as unknown as {\n userId: string\n newName: string\n }\n const result = await integration.updateName({\n userId: params.userId,\n newName: params.newName,\n })\n return { data: result, status: 200 }\n }\n\n case 'getClaimedSeats': {\n if (!integration.getClaimedSeats) {\n return {\n data: { error: 'Method not implemented: getClaimedSeats' },\n status: 501,\n }\n }\n const bulkCouponId = (body as unknown as { bulkCouponId: string })\n .bulkCouponId\n const result = await integration.getClaimedSeats(bulkCouponId)\n return { data: result, status: 200 }\n }\n\n case 'searchContent': {\n if (!integration.searchContent) {\n return {\n data: { error: 'Method not implemented: searchContent' },\n status: 501,\n }\n }\n const params = body as unknown as ContentSearchRequest\n const result: ContentSearchResponse =\n await integration.searchContent(params)\n return { data: result, status: 200 }\n }\n\n case 'getProductStatus': {\n if (!integration.getProductStatus) {\n return {\n data: { error: 'Method not implemented: getProductStatus' },\n status: 501,\n }\n }\n const productId = (body as unknown as { productId: string }).productId\n const result = await integration.getProductStatus(productId)\n return { data: result, status: 200 }\n }\n\n default:\n return {\n data: { error: `Unknown action: ${action}` },\n status: 400,\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n return {\n data: { error: message },\n status: 500,\n }\n }\n}\n\n/**\n * Helper to create JSON responses\n */\nfunction jsonResponse(data: unknown, status: number): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: {\n 'content-type': 'application/json',\n },\n })\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/handler.ts"],"sourcesContent":["import { timingSafeEqual } from 'crypto'\nimport type { SupportIntegration } from './integration'\nimport type { ContentSearchRequest, ContentSearchResponse } from './types'\n\n/**\n * Configuration for createSupportHandler\n */\nexport interface SupportHandlerConfig {\n integration: SupportIntegration\n webhookSecret: string\n}\n\n/**\n * Request body for webhook actions\n */\ninterface WebhookRequest {\n action: string\n [key: string]: unknown\n}\n\n/**\n * Creates a Next.js API route handler for SupportIntegration.\n * Verifies HMAC-SHA256 signature and routes actions to integration methods.\n *\n * Signature format: timestamp=1234567890,v1=hex_signature\n * Payload to sign: timestamp.JSON.stringify(body)\n * Replay protection: 5 minute window\n *\n * @example\n * ```typescript\n * import { createSupportHandler } from '@skillrecordings/sdk/handler'\n * import { integration } from './integration'\n *\n * export const POST = createSupportHandler({\n * integration,\n * webhookSecret: process.env.SUPPORT_WEBHOOK_SECRET!,\n * })\n * ```\n */\nexport function createSupportHandler(\n config: SupportHandlerConfig\n): (request: Request) => Promise<Response> {\n const { integration, webhookSecret } = config\n\n return async function handler(request: Request): Promise<Response> {\n try {\n // 1. Extract signature header\n const signatureHeader = request.headers.get('x-support-signature')\n if (!signatureHeader) {\n return jsonResponse({ error: 'Missing signature header' }, 401)\n }\n\n // 2. Parse signature header (format: timestamp=1234567890,v1=hex_signature)\n const parts = signatureHeader.split(',')\n const timestampPart = parts.find((p) => p.startsWith('timestamp='))\n const signaturePart = parts.find((p) => p.startsWith('v1='))\n\n if (!timestampPart || !signaturePart) {\n return jsonResponse({ error: 'Invalid signature format' }, 401)\n }\n\n const timestampValue = timestampPart.split('=')[1]\n const signatureValue = signaturePart.split('=')[1]\n\n if (!timestampValue || !signatureValue) {\n return jsonResponse({ error: 'Invalid signature format' }, 401)\n }\n\n const timestamp = parseInt(timestampValue, 10)\n const receivedSignature = signatureValue\n\n // 3. Verify timestamp (replay protection - 5 minute window)\n const now = Math.floor(Date.now() / 1000)\n const maxAge = 300 // 5 minutes in seconds\n if (now - timestamp > maxAge) {\n return jsonResponse({ error: 'Signature expired' }, 401)\n }\n\n // 4. Read and parse body\n const bodyText = await request.text()\n let body: WebhookRequest\n\n try {\n body = JSON.parse(bodyText)\n } catch (err) {\n return jsonResponse({ error: 'Invalid JSON body' }, 400)\n }\n\n // 5. Compute expected signature\n const crypto = await import('crypto')\n const payload = `${timestamp}.${bodyText}`\n const expectedSignature = crypto\n .createHmac('sha256', webhookSecret)\n .update(payload)\n .digest('hex')\n\n // 6. Timing-safe comparison to prevent timing attacks\n if (\n !timingSafeEqual(\n Buffer.from(receivedSignature),\n Buffer.from(expectedSignature)\n )\n ) {\n return jsonResponse({ error: 'Invalid signature' }, 401)\n }\n\n // 7. Extract action field\n const { action } = body\n if (!action || typeof action !== 'string') {\n return jsonResponse({ error: 'Missing action field' }, 400)\n }\n\n // 8. Route to integration method\n const result = await routeAction(integration, action, body)\n return jsonResponse(result.data, result.status)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n return jsonResponse({ error: `Internal error: ${message}` }, 500)\n }\n }\n}\n\n/**\n * Routes action to appropriate integration method\n */\nasync function routeAction(\n integration: SupportIntegration,\n action: string,\n body: WebhookRequest\n): Promise<{ data: unknown; status: number }> {\n try {\n switch (action) {\n case 'lookupUser': {\n const email = (body as unknown as { email: string }).email\n const result = await integration.lookupUser(email)\n return { data: result, status: 200 }\n }\n\n case 'getPurchases': {\n const userId = (body as unknown as { userId: string }).userId\n const result = await integration.getPurchases(userId)\n return { data: result, status: 200 }\n }\n\n case 'revokeAccess': {\n const params = body as unknown as {\n purchaseId: string\n reason: string\n refundId: string\n }\n const result = await integration.revokeAccess({\n purchaseId: params.purchaseId,\n reason: params.reason,\n refundId: params.refundId,\n })\n return { data: result, status: 200 }\n }\n\n case 'transferPurchase': {\n const params = body as unknown as {\n purchaseId: string\n fromUserId: string\n toEmail: string\n }\n const result = await integration.transferPurchase({\n purchaseId: params.purchaseId,\n fromUserId: params.fromUserId,\n toEmail: params.toEmail,\n })\n return { data: result, status: 200 }\n }\n\n case 'generateMagicLink': {\n const params = body as unknown as {\n email: string\n expiresIn: number\n }\n const result = await integration.generateMagicLink({\n email: params.email,\n expiresIn: params.expiresIn,\n })\n return { data: result, status: 200 }\n }\n\n // Optional methods\n case 'getSubscriptions': {\n if (!integration.getSubscriptions) {\n return {\n data: { error: 'Method not implemented: getSubscriptions' },\n status: 501,\n }\n }\n const userId = (body as unknown as { userId: string }).userId\n const result = await integration.getSubscriptions(userId)\n return { data: result, status: 200 }\n }\n\n case 'updateEmail': {\n if (!integration.updateEmail) {\n return {\n data: { error: 'Method not implemented: updateEmail' },\n status: 501,\n }\n }\n const params = body as unknown as {\n userId: string\n newEmail: string\n }\n const result = await integration.updateEmail({\n userId: params.userId,\n newEmail: params.newEmail,\n })\n return { data: result, status: 200 }\n }\n\n case 'updateName': {\n if (!integration.updateName) {\n return {\n data: { error: 'Method not implemented: updateName' },\n status: 501,\n }\n }\n const params = body as unknown as {\n userId: string\n newName: string\n }\n const result = await integration.updateName({\n userId: params.userId,\n newName: params.newName,\n })\n return { data: result, status: 200 }\n }\n\n case 'getClaimedSeats': {\n if (!integration.getClaimedSeats) {\n return {\n data: { error: 'Method not implemented: getClaimedSeats' },\n status: 501,\n }\n }\n const bulkCouponId = (body as unknown as { bulkCouponId: string })\n .bulkCouponId\n const result = await integration.getClaimedSeats(bulkCouponId)\n return { data: result, status: 200 }\n }\n\n case 'searchContent': {\n if (!integration.searchContent) {\n return {\n data: { error: 'Method not implemented: searchContent' },\n status: 501,\n }\n }\n const params = body as unknown as ContentSearchRequest\n const result: ContentSearchResponse =\n await integration.searchContent(params)\n return { data: result, status: 200 }\n }\n\n case 'getProductStatus': {\n if (!integration.getProductStatus) {\n return {\n data: { error: 'Method not implemented: getProductStatus' },\n status: 501,\n }\n }\n const productId = (body as unknown as { productId: string }).productId\n const result = await integration.getProductStatus(productId)\n return { data: result, status: 200 }\n }\n\n // ── Agent Intelligence Methods ─────────────────────────────────\n\n case 'getActivePromotions': {\n if (!integration.getActivePromotions) {\n return {\n data: { error: 'Method not implemented: getActivePromotions' },\n status: 501,\n }\n }\n const result = await integration.getActivePromotions()\n return { data: result, status: 200 }\n }\n\n case 'getCouponInfo': {\n if (!integration.getCouponInfo) {\n return {\n data: { error: 'Method not implemented: getCouponInfo' },\n status: 501,\n }\n }\n const code = (body as unknown as { code: string }).code\n const result = await integration.getCouponInfo(code)\n return { data: result, status: 200 }\n }\n\n case 'getRefundPolicy': {\n if (!integration.getRefundPolicy) {\n return {\n data: { error: 'Method not implemented: getRefundPolicy' },\n status: 501,\n }\n }\n const result = await integration.getRefundPolicy()\n return { data: result, status: 200 }\n }\n\n case 'getContentAccess': {\n if (!integration.getContentAccess) {\n return {\n data: { error: 'Method not implemented: getContentAccess' },\n status: 501,\n }\n }\n const userId = (body as unknown as { userId: string }).userId\n const result = await integration.getContentAccess(userId)\n return { data: result, status: 200 }\n }\n\n case 'getRecentActivity': {\n if (!integration.getRecentActivity) {\n return {\n data: { error: 'Method not implemented: getRecentActivity' },\n status: 501,\n }\n }\n const userId = (body as unknown as { userId: string }).userId\n const result = await integration.getRecentActivity(userId)\n return { data: result, status: 200 }\n }\n\n case 'getLicenseInfo': {\n if (!integration.getLicenseInfo) {\n return {\n data: { error: 'Method not implemented: getLicenseInfo' },\n status: 501,\n }\n }\n const purchaseId = (body as unknown as { purchaseId: string })\n .purchaseId\n const result = await integration.getLicenseInfo(purchaseId)\n return { data: result, status: 200 }\n }\n\n case 'getAppInfo': {\n if (!integration.getAppInfo) {\n return {\n data: { error: 'Method not implemented: getAppInfo' },\n status: 501,\n }\n }\n const result = await integration.getAppInfo()\n return { data: result, status: 200 }\n }\n\n default:\n return {\n data: { error: `Unknown action: ${action}` },\n status: 400,\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n return {\n data: { error: message },\n status: 500,\n }\n }\n}\n\n/**\n * Helper to create JSON responses\n */\nfunction jsonResponse(data: unknown, status: number): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: {\n 'content-type': 'application/json',\n },\n })\n}\n"],"mappings":"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","names":[]}
|
package/dist/integration.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { User, Purchase, Subscription, ActionResult, ClaimedSeat, ContentSearchRequest, ContentSearchResponse, ProductStatus } from './types.js';
|
|
1
|
+
import { User, Purchase, Subscription, ActionResult, ClaimedSeat, ContentSearchRequest, ContentSearchResponse, ProductStatus, Promotion, CouponInfo, RefundPolicy, ContentAccess, UserActivity, LicenseInfo, AppInfo } from './types.js';
|
|
2
2
|
export { ContentSearchResult } from './types.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
@@ -191,6 +191,83 @@ interface SupportIntegration {
|
|
|
191
191
|
* ```
|
|
192
192
|
*/
|
|
193
193
|
getProductStatus?(productId: string): Promise<ProductStatus | null>;
|
|
194
|
+
/**
|
|
195
|
+
* Get currently active promotions and sales.
|
|
196
|
+
* Optional method - implement to let the agent answer presales pricing questions.
|
|
197
|
+
*
|
|
198
|
+
* @returns Array of active promotions, empty if none
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* // Customer asks: "Do you have any discounts?"
|
|
203
|
+
* const promos = await integration.getActivePromotions()
|
|
204
|
+
* // Agent: "Yes! We have a 30% off summer sale running until August 1st."
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
getActivePromotions?(): Promise<Promotion[]>;
|
|
208
|
+
/**
|
|
209
|
+
* Look up a coupon or discount code.
|
|
210
|
+
* Optional method - implement to let the agent validate coupon codes.
|
|
211
|
+
*
|
|
212
|
+
* @param code - Coupon code to look up
|
|
213
|
+
* @returns CouponInfo if found, null if code doesn't exist
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* // Customer asks: "Is the code SUMMER2025 still valid?"
|
|
218
|
+
* const coupon = await integration.getCouponInfo('SUMMER2025')
|
|
219
|
+
* if (coupon?.valid) {
|
|
220
|
+
* // Agent: "Yes, SUMMER2025 gives you 30% off!"
|
|
221
|
+
* }
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
getCouponInfo?(code: string): Promise<CouponInfo | null>;
|
|
225
|
+
/**
|
|
226
|
+
* Get the app's refund policy configuration.
|
|
227
|
+
* Optional method - implement to give the agent accurate refund window info.
|
|
228
|
+
*
|
|
229
|
+
* Without this, the agent falls back to hardcoded defaults which may be wrong.
|
|
230
|
+
*
|
|
231
|
+
* @returns RefundPolicy with window sizes and conditions
|
|
232
|
+
*/
|
|
233
|
+
getRefundPolicy?(): Promise<RefundPolicy>;
|
|
234
|
+
/**
|
|
235
|
+
* Get granular content access for a user.
|
|
236
|
+
* Optional method - implement to let the agent debug access issues precisely.
|
|
237
|
+
*
|
|
238
|
+
* Goes beyond purchase data to show what content a user can actually access,
|
|
239
|
+
* including module-level access and team membership.
|
|
240
|
+
*
|
|
241
|
+
* @param userId - User's unique identifier
|
|
242
|
+
* @returns ContentAccess with per-product and per-module access levels
|
|
243
|
+
*/
|
|
244
|
+
getContentAccess?(userId: string): Promise<ContentAccess>;
|
|
245
|
+
/**
|
|
246
|
+
* Get recent user activity and progress.
|
|
247
|
+
* Optional method - implement to let the agent assess product usage.
|
|
248
|
+
*
|
|
249
|
+
* Useful for debugging access issues ("when did they last log in?")
|
|
250
|
+
* and refund triage ("have they used the product?").
|
|
251
|
+
*
|
|
252
|
+
* @param userId - User's unique identifier
|
|
253
|
+
* @returns UserActivity with login, completion, and recent action data
|
|
254
|
+
*/
|
|
255
|
+
getRecentActivity?(userId: string): Promise<UserActivity>;
|
|
256
|
+
/**
|
|
257
|
+
* Get team license and seat information for a purchase.
|
|
258
|
+
* Optional method - implement to let the agent answer team/enterprise questions.
|
|
259
|
+
*
|
|
260
|
+
* @param purchaseId - Purchase identifier for the team license
|
|
261
|
+
* @returns LicenseInfo with seat allocation, or null if not a team purchase
|
|
262
|
+
*/
|
|
263
|
+
getLicenseInfo?(purchaseId: string): Promise<LicenseInfo | null>;
|
|
264
|
+
/**
|
|
265
|
+
* Get app metadata (name, URLs, instructor info).
|
|
266
|
+
* Optional method - eliminates hardcoded product names and URLs in agent prompts.
|
|
267
|
+
*
|
|
268
|
+
* @returns AppInfo with app name, instructor, and relevant URLs
|
|
269
|
+
*/
|
|
270
|
+
getAppInfo?(): Promise<AppInfo>;
|
|
194
271
|
}
|
|
195
272
|
|
|
196
|
-
export { ActionResult, ClaimedSeat, ContentSearchRequest, ContentSearchResponse, ProductStatus, Purchase, Subscription, type SupportIntegration, User };
|
|
273
|
+
export { ActionResult, AppInfo, ClaimedSeat, ContentAccess, ContentSearchRequest, ContentSearchResponse, CouponInfo, LicenseInfo, ProductStatus, Promotion, Purchase, RefundPolicy, Subscription, type SupportIntegration, User, UserActivity };
|