@goliapkg/sentori-next 0.3.0 → 1.1.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 +5 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +6 -0
- package/lib/config.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -0
- package/lib/index.js.map +1 -1
- package/lib/push.d.ts +14 -0
- package/lib/push.d.ts.map +1 -0
- package/lib/push.js +90 -0
- package/lib/push.js.map +1 -0
- package/package.json +10 -5
- package/src/config.ts +15 -2
- package/src/index.ts +8 -0
- package/src/push.ts +132 -0
package/README.md
CHANGED
|
@@ -137,3 +137,8 @@ Tracks the underlying SDKs:
|
|
|
137
137
|
- depends on `@goliapkg/sentori-react@0.1.0`
|
|
138
138
|
- depends on `@goliapkg/sentori-javascript@0.2.0`
|
|
139
139
|
- depends on `@goliapkg/sentori-core@0.1.0`
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
Dual-licensed under [Apache-2.0](../../LICENSE-APACHE) OR
|
|
144
|
+
[MIT](../../LICENSE-MIT).
|
package/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D,MAAM,MAAM,IAAI,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEtC,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG;IAC3D,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACjD,CAAA;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D,MAAM,MAAM,IAAI,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEtC,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG;IAC3D,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACjD,CAAA;AAkBD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,GAAE,iBAAsB,GAAG,iBAAiB,CAsCxF"}
|
package/lib/config.js
CHANGED
|
@@ -39,6 +39,12 @@ export function resolveConfig(side, cfg = {}) {
|
|
|
39
39
|
if (v)
|
|
40
40
|
out[k] = v;
|
|
41
41
|
}
|
|
42
|
+
// v2.0 W3 — `capture` is nested, env can't drive it. Carry the
|
|
43
|
+
// explicit value through so callers can still pass
|
|
44
|
+
// `capture: { trackAutoBreadcrumb: true }` to resolveConfig().
|
|
45
|
+
if (cfg.capture !== undefined) {
|
|
46
|
+
out.capture = cfg.capture;
|
|
47
|
+
}
|
|
42
48
|
// Defaults: ingestUrl points at the public SaaS if nothing was set.
|
|
43
49
|
if (!out.ingestUrl)
|
|
44
50
|
out.ingestUrl = 'https://ingest.sentori.golia.jp';
|
package/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,oEAAoE;AACpE,sEAAsE;AACtE,mEAAmE;AACnE,oEAAoE;AACpE,sEAAsE;AACtE,uBAAuB;AAWvB,MAAM,aAAa,GAAG,sBAAsB,CAAA;AAC5C,MAAM,aAAa,GAAG,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,oEAAoE;AACpE,sEAAsE;AACtE,mEAAmE;AACnE,oEAAoE;AACpE,sEAAsE;AACtE,uBAAuB;AAWvB,MAAM,aAAa,GAAG,sBAAsB,CAAA;AAC5C,MAAM,aAAa,GAAG,UAAU,CAAA;AAQhC,MAAM,OAAO,GAAiC;IAC5C,WAAW,EAAE,aAAa;IAC1B,SAAS,EAAE,YAAY;IACvB,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,OAAO;CACf,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,IAAU,EAAE,MAAyB,EAAE;IACnE,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,IAAI,UAAU,EAAE,CAAA;IAC3C,MAAM,GAAG,GAA+B,EAAE,CAAA;IAE1C,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAmB,EAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;QACvB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAA;YACjB,SAAQ;QACV,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;QACzB,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,aAAa,GAAG,MAAM,EAAE,CAAC,CAAA;QAChD,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,aAAa,GAAG,MAAM,EAAE,CAAC,CAAA;QAC/C,MAAM,CAAC,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,OAAO,CAAC,CAAA;QAC3D,IAAI,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACnB,CAAC;IAED,+DAA+D;IAC/D,mDAAmD;IACnD,+DAA+D;IAC/D,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC9B,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;IAC3B,CAAC;IAED,oEAAoE;IACpE,IAAI,CAAC,GAAG,CAAC,SAAS;QAAE,GAAG,CAAC,SAAS,GAAG,iCAAiC,CAAA;IAErE,KAAK,MAAM,QAAQ,IAAI,CAAC,aAAa,EAAE,SAAS,EAAE,OAAO,CAAU,EAAE,CAAC;QACpE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACb,wCAAwC,QAAQ,SAAS;gBACvD,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAC3E,wBAAwB,CAC3B,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,GAAwB,CAAA;AACjC,CAAC;AAED,SAAS,UAAU;IACjB,mEAAmE;IACnE,mEAAmE;IACnE,MAAM,CAAC,GAAI,UAAyE,CAAC,OAAO,CAAA;IAC5F,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,CAAA;AACrB,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export { resolveConfig } from './config.js';
|
|
|
4
4
|
export type { SentoriNextConfig } from './config.js';
|
|
5
5
|
export type { RequestErrorContext, RequestErrorRequest } from './server.js';
|
|
6
6
|
export { SentoriErrorBoundary, SentoriProvider, useCaptureError, useSentori, } from '@goliapkg/sentori-react';
|
|
7
|
+
export { sentoriPush } from './push.js';
|
|
8
|
+
export type { SentoriPushConfig, SentoriPushClient } from './push.js';
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAE3E,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,UAAU,GACX,MAAM,yBAAyB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAE3E,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,UAAU,GACX,MAAM,yBAAyB,CAAA;AAOhC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA"}
|
package/lib/index.js
CHANGED
|
@@ -11,4 +11,10 @@ export { clientInit } from './client.js';
|
|
|
11
11
|
export { serverInit, onRequestError } from './server.js';
|
|
12
12
|
export { resolveConfig } from './config.js';
|
|
13
13
|
export { SentoriErrorBoundary, SentoriProvider, useCaptureError, useSentori, } from '@goliapkg/sentori-react';
|
|
14
|
+
// v2.8 — server-side Push helper. Re-export from the dedicated
|
|
15
|
+
// `/push` subpath so server-only code can `import { sentoriPush }
|
|
16
|
+
// from '@goliapkg/sentori-next/push'` and avoid pulling the rest of
|
|
17
|
+
// the surface. The top-level re-export here keeps `import { ... }
|
|
18
|
+
// from '@goliapkg/sentori-next'` working for the common case.
|
|
19
|
+
export { sentoriPush } from './push.js';
|
|
14
20
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,+DAA+D;AAC/D,EAAE;AACF,wEAAwE;AACxE,yEAAyE;AACzE,6EAA6E;AAC7E,EAAE;AACF,mEAAmE;AACnE,8DAA8D;AAE9D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAK3C,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,UAAU,GACX,MAAM,yBAAyB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,+DAA+D;AAC/D,EAAE;AACF,wEAAwE;AACxE,yEAAyE;AACzE,6EAA6E;AAC7E,EAAE;AACF,mEAAmE;AACnE,8DAA8D;AAE9D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAK3C,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,UAAU,GACX,MAAM,yBAAyB,CAAA;AAEhC,+DAA+D;AAC/D,kEAAkE;AAClE,oEAAoE;AACpE,kEAAkE;AAClE,8DAA8D;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA"}
|
package/lib/push.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PushMessage, PushReceipt, PushTicket } from '@goliapkg/sentori-core';
|
|
2
|
+
export type SentoriPushConfig = {
|
|
3
|
+
ingestUrl: string;
|
|
4
|
+
token: string;
|
|
5
|
+
fetch?: typeof fetch;
|
|
6
|
+
};
|
|
7
|
+
export type SentoriPushClient = {
|
|
8
|
+
send(msg: PushMessage): Promise<PushTicket>;
|
|
9
|
+
sendBatch(msgs: PushMessage[]): Promise<PushTicket[]>;
|
|
10
|
+
getReceipt(sendId: string): Promise<PushReceipt>;
|
|
11
|
+
isSentoriPushToken(value: unknown): value is string;
|
|
12
|
+
};
|
|
13
|
+
export declare function sentoriPush(cfg: SentoriPushConfig): SentoriPushClient;
|
|
14
|
+
//# sourceMappingURL=push.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACX,MAAM,wBAAwB,CAAA;AAE/B,MAAM,MAAM,iBAAiB,GAAG;IAG9B,SAAS,EAAE,MAAM,CAAA;IAIjB,KAAK,EAAE,MAAM,CAAA;IAIb,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAI9B,IAAI,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IAM3C,SAAS,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IAErD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAEhD,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAA;CACpD,CAAA;AAID,wBAAgB,WAAW,CAAC,GAAG,EAAE,iBAAiB,GAAG,iBAAiB,CAgErE"}
|
package/lib/push.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// v2.8 — server-side Push helper for Next.js apps.
|
|
2
|
+
//
|
|
3
|
+
// Use from API routes, Server Actions, or any Node / Edge runtime
|
|
4
|
+
// piece that needs to send a push. The helper wraps `/v1/push/send`
|
|
5
|
+
// + `/v1/push/receipts/{id}` with the same wire shape the SDK
|
|
6
|
+
// matrix shares (see `@goliapkg/sentori-core`'s `PushMessage` type).
|
|
7
|
+
//
|
|
8
|
+
// Edge-safe: pure `fetch`, no Node-only imports. Works under
|
|
9
|
+
// `runtime: 'edge'` for App Router + middleware contexts.
|
|
10
|
+
//
|
|
11
|
+
// Example (App Router server action):
|
|
12
|
+
// 'use server'
|
|
13
|
+
// import { sentoriPush } from '@goliapkg/sentori-next/push'
|
|
14
|
+
//
|
|
15
|
+
// const push = sentoriPush({
|
|
16
|
+
// ingestUrl: process.env.SENTORI_INGEST_URL!,
|
|
17
|
+
// token: process.env.SENTORI_ADMIN_TOKEN!,
|
|
18
|
+
// })
|
|
19
|
+
//
|
|
20
|
+
// export async function notifyComment(iptHandle: string, comment: string) {
|
|
21
|
+
// await push.send({
|
|
22
|
+
// to: iptHandle,
|
|
23
|
+
// title: 'New comment',
|
|
24
|
+
// body: comment.slice(0, 80),
|
|
25
|
+
// data: { kind: 'comment' },
|
|
26
|
+
// })
|
|
27
|
+
// }
|
|
28
|
+
const MAX_CONCURRENT_BATCH = 8;
|
|
29
|
+
export function sentoriPush(cfg) {
|
|
30
|
+
const fetchImpl = cfg.fetch ?? globalThis.fetch;
|
|
31
|
+
if (!fetchImpl) {
|
|
32
|
+
throw new Error('sentoriPush: no fetch implementation available');
|
|
33
|
+
}
|
|
34
|
+
const base = cfg.ingestUrl.replace(/\/+$/, '');
|
|
35
|
+
async function send(msg) {
|
|
36
|
+
const res = await fetchImpl(`${base}/v1/push/send`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
authorization: `Bearer ${cfg.token}`,
|
|
40
|
+
'content-type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify(msg),
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
const detail = await res.text().catch(() => '');
|
|
46
|
+
throw new Error(`/v1/push/send HTTP ${res.status}: ${detail.slice(0, 200)}`);
|
|
47
|
+
}
|
|
48
|
+
const body = (await res.json());
|
|
49
|
+
if (!body.tickets || body.tickets.length === 0) {
|
|
50
|
+
throw new Error('server returned no tickets');
|
|
51
|
+
}
|
|
52
|
+
return body.tickets[0];
|
|
53
|
+
}
|
|
54
|
+
async function sendBatch(msgs) {
|
|
55
|
+
// Pool of workers — each picks the next message off `queue`.
|
|
56
|
+
const queue = msgs.slice();
|
|
57
|
+
const results = new Array(msgs.length);
|
|
58
|
+
let nextSlot = 0;
|
|
59
|
+
const workers = [];
|
|
60
|
+
const worker = async () => {
|
|
61
|
+
while (true) {
|
|
62
|
+
const idx = nextSlot++;
|
|
63
|
+
const msg = queue[idx];
|
|
64
|
+
if (!msg)
|
|
65
|
+
return;
|
|
66
|
+
results[idx] = await send(msg);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
for (let i = 0; i < Math.min(MAX_CONCURRENT_BATCH, msgs.length); i++) {
|
|
70
|
+
workers.push(worker());
|
|
71
|
+
}
|
|
72
|
+
await Promise.all(workers);
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
async function getReceipt(sendId) {
|
|
76
|
+
const res = await fetchImpl(`${base}/v1/push/receipts/${encodeURIComponent(sendId)}`, {
|
|
77
|
+
headers: { authorization: `Bearer ${cfg.token}` },
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
const detail = await res.text().catch(() => '');
|
|
81
|
+
throw new Error(`/v1/push/receipts HTTP ${res.status}: ${detail.slice(0, 200)}`);
|
|
82
|
+
}
|
|
83
|
+
return (await res.json());
|
|
84
|
+
}
|
|
85
|
+
function isSentoriPushToken(value) {
|
|
86
|
+
return typeof value === 'string' && /^ipt_[0-9a-fA-F]+$/.test(value);
|
|
87
|
+
}
|
|
88
|
+
return { send, sendBatch, getReceipt, isSentoriPushToken };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=push.js.map
|
package/lib/push.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../src/push.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goliapkg/sentori-next",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Next.js adapter for Sentori — instrumentation.ts hooks, App Router error boundary, navigation tracing, env-driven provider built on @goliapkg/sentori-react.",
|
|
5
|
-
"license": "MIT",
|
|
5
|
+
"license": "Apache-2.0 OR MIT",
|
|
6
|
+
"author": "GOLIA K.K. <takagi@golia.jp> (https://golia.jp)",
|
|
6
7
|
"homepage": "https://sentori.golia.jp",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
@@ -42,6 +43,10 @@
|
|
|
42
43
|
"./app-router": {
|
|
43
44
|
"types": "./lib/app-router.d.ts",
|
|
44
45
|
"default": "./lib/app-router.js"
|
|
46
|
+
},
|
|
47
|
+
"./push": {
|
|
48
|
+
"types": "./lib/push.d.ts",
|
|
49
|
+
"default": "./lib/push.js"
|
|
45
50
|
}
|
|
46
51
|
},
|
|
47
52
|
"files": [
|
|
@@ -60,9 +65,9 @@
|
|
|
60
65
|
"react": ">=18"
|
|
61
66
|
},
|
|
62
67
|
"dependencies": {
|
|
63
|
-
"@goliapkg/sentori-core": "^
|
|
64
|
-
"@goliapkg/sentori-javascript": "^
|
|
65
|
-
"@goliapkg/sentori-react": "^
|
|
68
|
+
"@goliapkg/sentori-core": "^1.3.0",
|
|
69
|
+
"@goliapkg/sentori-javascript": "^1.3.0",
|
|
70
|
+
"@goliapkg/sentori-react": "^1.1.0"
|
|
66
71
|
},
|
|
67
72
|
"devDependencies": {
|
|
68
73
|
"@types/bun": "latest",
|
package/src/config.ts
CHANGED
|
@@ -19,7 +19,13 @@ export type SentoriNextConfig = Partial<CommonInitOptions> & {
|
|
|
19
19
|
const CLIENT_PREFIX = 'NEXT_PUBLIC_SENTORI_'
|
|
20
20
|
const SERVER_PREFIX = 'SENTORI_'
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
/** v2.0 W3 — CommonInitOptions now has a nested `capture` object that
|
|
23
|
+
* can't be resolved from a single env var, so KEY_MAP restricts to
|
|
24
|
+
* the four primitive-string fields the env layer actually drives.
|
|
25
|
+
* Nested options stay explicit-only via `cfg`. */
|
|
26
|
+
type EnvDrivenKey = 'environment' | 'ingestUrl' | 'release' | 'token'
|
|
27
|
+
|
|
28
|
+
const KEY_MAP: Record<EnvDrivenKey, string> = {
|
|
23
29
|
environment: 'ENVIRONMENT',
|
|
24
30
|
ingestUrl: 'INGEST_URL',
|
|
25
31
|
release: 'RELEASE',
|
|
@@ -39,7 +45,7 @@ export function resolveConfig(side: Side, cfg: SentoriNextConfig = {}): CommonIn
|
|
|
39
45
|
const env = cfg.envOverride ?? processEnv()
|
|
40
46
|
const out: Partial<CommonInitOptions> = {}
|
|
41
47
|
|
|
42
|
-
for (const k of Object.keys(KEY_MAP) as
|
|
48
|
+
for (const k of Object.keys(KEY_MAP) as EnvDrivenKey[]) {
|
|
43
49
|
const explicit = cfg[k]
|
|
44
50
|
if (explicit !== undefined) {
|
|
45
51
|
out[k] = explicit
|
|
@@ -52,6 +58,13 @@ export function resolveConfig(side: Side, cfg: SentoriNextConfig = {}): CommonIn
|
|
|
52
58
|
if (v) out[k] = v
|
|
53
59
|
}
|
|
54
60
|
|
|
61
|
+
// v2.0 W3 — `capture` is nested, env can't drive it. Carry the
|
|
62
|
+
// explicit value through so callers can still pass
|
|
63
|
+
// `capture: { trackAutoBreadcrumb: true }` to resolveConfig().
|
|
64
|
+
if (cfg.capture !== undefined) {
|
|
65
|
+
out.capture = cfg.capture
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
// Defaults: ingestUrl points at the public SaaS if nothing was set.
|
|
56
69
|
if (!out.ingestUrl) out.ingestUrl = 'https://ingest.sentori.golia.jp'
|
|
57
70
|
|
package/src/index.ts
CHANGED
|
@@ -21,3 +21,11 @@ export {
|
|
|
21
21
|
useCaptureError,
|
|
22
22
|
useSentori,
|
|
23
23
|
} from '@goliapkg/sentori-react'
|
|
24
|
+
|
|
25
|
+
// v2.8 — server-side Push helper. Re-export from the dedicated
|
|
26
|
+
// `/push` subpath so server-only code can `import { sentoriPush }
|
|
27
|
+
// from '@goliapkg/sentori-next/push'` and avoid pulling the rest of
|
|
28
|
+
// the surface. The top-level re-export here keeps `import { ... }
|
|
29
|
+
// from '@goliapkg/sentori-next'` working for the common case.
|
|
30
|
+
export { sentoriPush } from './push.js'
|
|
31
|
+
export type { SentoriPushConfig, SentoriPushClient } from './push.js'
|
package/src/push.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// v2.8 — server-side Push helper for Next.js apps.
|
|
2
|
+
//
|
|
3
|
+
// Use from API routes, Server Actions, or any Node / Edge runtime
|
|
4
|
+
// piece that needs to send a push. The helper wraps `/v1/push/send`
|
|
5
|
+
// + `/v1/push/receipts/{id}` with the same wire shape the SDK
|
|
6
|
+
// matrix shares (see `@goliapkg/sentori-core`'s `PushMessage` type).
|
|
7
|
+
//
|
|
8
|
+
// Edge-safe: pure `fetch`, no Node-only imports. Works under
|
|
9
|
+
// `runtime: 'edge'` for App Router + middleware contexts.
|
|
10
|
+
//
|
|
11
|
+
// Example (App Router server action):
|
|
12
|
+
// 'use server'
|
|
13
|
+
// import { sentoriPush } from '@goliapkg/sentori-next/push'
|
|
14
|
+
//
|
|
15
|
+
// const push = sentoriPush({
|
|
16
|
+
// ingestUrl: process.env.SENTORI_INGEST_URL!,
|
|
17
|
+
// token: process.env.SENTORI_ADMIN_TOKEN!,
|
|
18
|
+
// })
|
|
19
|
+
//
|
|
20
|
+
// export async function notifyComment(iptHandle: string, comment: string) {
|
|
21
|
+
// await push.send({
|
|
22
|
+
// to: iptHandle,
|
|
23
|
+
// title: 'New comment',
|
|
24
|
+
// body: comment.slice(0, 80),
|
|
25
|
+
// data: { kind: 'comment' },
|
|
26
|
+
// })
|
|
27
|
+
// }
|
|
28
|
+
|
|
29
|
+
import type {
|
|
30
|
+
PushMessage,
|
|
31
|
+
PushReceipt,
|
|
32
|
+
PushTicket,
|
|
33
|
+
} from '@goliapkg/sentori-core'
|
|
34
|
+
|
|
35
|
+
export type SentoriPushConfig = {
|
|
36
|
+
/// Base URL of the Sentori ingest host. e.g. `https://ingest.sentori.golia.jp`.
|
|
37
|
+
/// Typically read from `process.env.SENTORI_INGEST_URL`.
|
|
38
|
+
ingestUrl: string
|
|
39
|
+
/// Admin Bearer token. The `/v1/push/send` route requires an
|
|
40
|
+
/// admin-scope token (the same kind that posts events).
|
|
41
|
+
/// Typically read from `process.env.SENTORI_ADMIN_TOKEN`.
|
|
42
|
+
token: string
|
|
43
|
+
/// Override the global fetch implementation. Defaults to
|
|
44
|
+
/// `globalThis.fetch`. Useful for unit tests + environments that
|
|
45
|
+
/// inject a fetch polyfill.
|
|
46
|
+
fetch?: typeof fetch
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type SentoriPushClient = {
|
|
50
|
+
/// Send one push. Returns the queued ticket (or the existing one
|
|
51
|
+
/// if the call carries an idempotency key that matched an earlier
|
|
52
|
+
/// send).
|
|
53
|
+
send(msg: PushMessage): Promise<PushTicket>
|
|
54
|
+
/// Send a batch — equivalent to N parallel `send` calls but uses
|
|
55
|
+
/// a single HTTP request when the message's `to` is an array. If
|
|
56
|
+
/// you pass an array of `PushMessage`s, this fans out to N
|
|
57
|
+
/// requests (one per message); concurrency-capped at 8 to avoid
|
|
58
|
+
/// flooding the Sentori dispatcher on big jobs.
|
|
59
|
+
sendBatch(msgs: PushMessage[]): Promise<PushTicket[]>
|
|
60
|
+
/// Fetch the latest status of a send by id.
|
|
61
|
+
getReceipt(sendId: string): Promise<PushReceipt>
|
|
62
|
+
/// `true` if `value` is a Sentori push handle (`ipt_...`).
|
|
63
|
+
isSentoriPushToken(value: unknown): value is string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const MAX_CONCURRENT_BATCH = 8
|
|
67
|
+
|
|
68
|
+
export function sentoriPush(cfg: SentoriPushConfig): SentoriPushClient {
|
|
69
|
+
const fetchImpl = cfg.fetch ?? globalThis.fetch
|
|
70
|
+
if (!fetchImpl) {
|
|
71
|
+
throw new Error('sentoriPush: no fetch implementation available')
|
|
72
|
+
}
|
|
73
|
+
const base = cfg.ingestUrl.replace(/\/+$/, '')
|
|
74
|
+
|
|
75
|
+
async function send(msg: PushMessage): Promise<PushTicket> {
|
|
76
|
+
const res = await fetchImpl(`${base}/v1/push/send`, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: {
|
|
79
|
+
authorization: `Bearer ${cfg.token}`,
|
|
80
|
+
'content-type': 'application/json',
|
|
81
|
+
},
|
|
82
|
+
body: JSON.stringify(msg),
|
|
83
|
+
})
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
const detail = await res.text().catch(() => '')
|
|
86
|
+
throw new Error(`/v1/push/send HTTP ${res.status}: ${detail.slice(0, 200)}`)
|
|
87
|
+
}
|
|
88
|
+
const body = (await res.json()) as { tickets?: PushTicket[] }
|
|
89
|
+
if (!body.tickets || body.tickets.length === 0) {
|
|
90
|
+
throw new Error('server returned no tickets')
|
|
91
|
+
}
|
|
92
|
+
return body.tickets[0]!
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function sendBatch(msgs: PushMessage[]): Promise<PushTicket[]> {
|
|
96
|
+
// Pool of workers — each picks the next message off `queue`.
|
|
97
|
+
const queue = msgs.slice()
|
|
98
|
+
const results: PushTicket[] = new Array(msgs.length)
|
|
99
|
+
let nextSlot = 0
|
|
100
|
+
const workers: Promise<void>[] = []
|
|
101
|
+
const worker = async (): Promise<void> => {
|
|
102
|
+
while (true) {
|
|
103
|
+
const idx = nextSlot++
|
|
104
|
+
const msg = queue[idx]
|
|
105
|
+
if (!msg) return
|
|
106
|
+
results[idx] = await send(msg)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (let i = 0; i < Math.min(MAX_CONCURRENT_BATCH, msgs.length); i++) {
|
|
110
|
+
workers.push(worker())
|
|
111
|
+
}
|
|
112
|
+
await Promise.all(workers)
|
|
113
|
+
return results
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function getReceipt(sendId: string): Promise<PushReceipt> {
|
|
117
|
+
const res = await fetchImpl(`${base}/v1/push/receipts/${encodeURIComponent(sendId)}`, {
|
|
118
|
+
headers: { authorization: `Bearer ${cfg.token}` },
|
|
119
|
+
})
|
|
120
|
+
if (!res.ok) {
|
|
121
|
+
const detail = await res.text().catch(() => '')
|
|
122
|
+
throw new Error(`/v1/push/receipts HTTP ${res.status}: ${detail.slice(0, 200)}`)
|
|
123
|
+
}
|
|
124
|
+
return (await res.json()) as PushReceipt
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isSentoriPushToken(value: unknown): value is string {
|
|
128
|
+
return typeof value === 'string' && /^ipt_[0-9a-fA-F]+$/.test(value)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { send, sendBatch, getReceipt, isSentoriPushToken }
|
|
132
|
+
}
|