@fhirfly-io/shl 0.4.0 → 0.6.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 +53 -0
- package/dist/{chunk-SK77O3SG.cjs → chunk-CN44QKWJ.cjs} +37 -4
- package/dist/chunk-CN44QKWJ.cjs.map +1 -0
- package/dist/{chunk-JOZ6XZPO.cjs → chunk-H37YQWF2.cjs} +33 -21
- package/dist/chunk-H37YQWF2.cjs.map +1 -0
- package/dist/{chunk-KGEFZQ6W.js → chunk-IYRQRY4A.js} +33 -22
- package/dist/chunk-IYRQRY4A.js.map +1 -0
- package/dist/{chunk-UU434UFQ.js → chunk-YUMCDN7I.js} +37 -4
- package/dist/chunk-YUMCDN7I.js.map +1 -0
- package/dist/cli.cjs +11 -11
- package/dist/cli.js +2 -2
- package/dist/express.cjs +2 -2
- package/dist/express.d.cts +2 -2
- package/dist/express.d.ts +2 -2
- package/dist/express.js +1 -1
- package/dist/fastify.cjs +2 -2
- package/dist/fastify.d.cts +2 -2
- package/dist/fastify.d.ts +2 -2
- package/dist/fastify.js +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -1
- package/dist/lambda.cjs +2 -2
- package/dist/lambda.d.cts +2 -2
- package/dist/lambda.d.ts +2 -2
- package/dist/lambda.js +1 -1
- package/dist/server.cjs +6 -2
- package/dist/server.d.cts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +1 -1
- package/dist/{storage-Cx7uXUl8.d.ts → storage-CvsOM1Eu.d.ts} +1 -1
- package/dist/{storage-BbzK-kFf.d.cts → storage-DggeMhOI.d.cts} +1 -1
- package/dist/{types-6Vw5fiat.d.ts → types--SjcaaWT.d.ts} +20 -2
- package/dist/{types-Cdi4IkC9.d.cts → types-BcfxBDTA.d.cts} +20 -2
- package/dist/{types-BLLJeWe_.d.ts → types-CmeXnyth.d.cts} +17 -3
- package/dist/{types-BLLJeWe_.d.cts → types-CmeXnyth.d.ts} +17 -3
- package/package.json +3 -3
- package/dist/chunk-JOZ6XZPO.cjs.map +0 -1
- package/dist/chunk-KGEFZQ6W.js.map +0 -1
- package/dist/chunk-SK77O3SG.cjs.map +0 -1
- package/dist/chunk-UU434UFQ.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkCN44QKWJ_cjs = require('./chunk-CN44QKWJ.cjs');
|
|
4
4
|
var chunkUDS6UJAL_cjs = require('./chunk-UDS6UJAL.cjs');
|
|
5
5
|
require('./chunk-Q7SFCCGT.cjs');
|
|
6
6
|
|
|
@@ -8,11 +8,11 @@ require('./chunk-Q7SFCCGT.cjs');
|
|
|
8
8
|
|
|
9
9
|
Object.defineProperty(exports, "IPS", {
|
|
10
10
|
enumerable: true,
|
|
11
|
-
get: function () { return
|
|
11
|
+
get: function () { return chunkCN44QKWJ_cjs.ips_exports; }
|
|
12
12
|
});
|
|
13
13
|
Object.defineProperty(exports, "SHL", {
|
|
14
14
|
enumerable: true,
|
|
15
|
-
get: function () { return
|
|
15
|
+
get: function () { return chunkCN44QKWJ_cjs.shl_exports; }
|
|
16
16
|
});
|
|
17
17
|
Object.defineProperty(exports, "EncryptionError", {
|
|
18
18
|
enumerable: true,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as SHLOptions, c as SHLResult, M as ManifestEntry, S as SHLStorage, d as
|
|
2
|
-
import { A as AzureStorage, a as AzureStorageConfig, F as FhirflyStorage, d as FhirflyStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, e as S3Storage, S as S3StorageConfig } from './storage-
|
|
1
|
+
import { b as SHLOptions, c as SHLResult, M as ManifestEntry, S as SHLStorage, E as EXPIRATION_PRESETS, d as ExpirationPreset, e as Manifest, f as SHLAttachment, a as SHLMetadata } from './types-CmeXnyth.cjs';
|
|
2
|
+
import { A as AzureStorage, a as AzureStorageConfig, F as FhirflyStorage, d as FhirflyStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, e as S3Storage, S as S3StorageConfig } from './storage-DggeMhOI.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shared FHIR R4 datatypes used across IPS bundle construction.
|
|
@@ -848,6 +848,8 @@ declare function revoke(shlId: string, storage: SHLStorage): Promise<void>;
|
|
|
848
848
|
declare const index_AzureStorage: typeof AzureStorage;
|
|
849
849
|
declare const index_AzureStorageConfig: typeof AzureStorageConfig;
|
|
850
850
|
type index_DecodedSHL = DecodedSHL;
|
|
851
|
+
declare const index_EXPIRATION_PRESETS: typeof EXPIRATION_PRESETS;
|
|
852
|
+
declare const index_ExpirationPreset: typeof ExpirationPreset;
|
|
851
853
|
declare const index_FhirflyStorage: typeof FhirflyStorage;
|
|
852
854
|
declare const index_FhirflyStorageConfig: typeof FhirflyStorageConfig;
|
|
853
855
|
declare const index_GCSStorage: typeof GCSStorage;
|
|
@@ -870,7 +872,7 @@ declare const index_decryptContent: typeof decryptContent;
|
|
|
870
872
|
declare const index_getEntryContent: typeof getEntryContent;
|
|
871
873
|
declare const index_revoke: typeof revoke;
|
|
872
874
|
declare namespace index {
|
|
873
|
-
export { index_AzureStorage as AzureStorage, index_AzureStorageConfig as AzureStorageConfig, type index_DecodedSHL as DecodedSHL, index_FhirflyStorage as FhirflyStorage, index_FhirflyStorageConfig as FhirflyStorageConfig, index_GCSStorage as GCSStorage, index_GCSStorageConfig as GCSStorageConfig, index_LocalStorage as LocalStorage, index_LocalStorageConfig as LocalStorageConfig, index_Manifest as Manifest, index_ManifestEntry as ManifestEntry, index_S3Storage as S3Storage, index_S3StorageConfig as S3StorageConfig, index_SHLAttachment as SHLAttachment, index_SHLMetadata as SHLMetadata, index_SHLOptions as SHLOptions, index_SHLResult as SHLResult, index_SHLStorage as SHLStorage, index_create as create, index_decode as decode, index_decrypt as decrypt, index_decryptContent as decryptContent, index_getEntryContent as getEntryContent, index_revoke as revoke };
|
|
875
|
+
export { index_AzureStorage as AzureStorage, index_AzureStorageConfig as AzureStorageConfig, type index_DecodedSHL as DecodedSHL, index_EXPIRATION_PRESETS as EXPIRATION_PRESETS, index_ExpirationPreset as ExpirationPreset, index_FhirflyStorage as FhirflyStorage, index_FhirflyStorageConfig as FhirflyStorageConfig, index_GCSStorage as GCSStorage, index_GCSStorageConfig as GCSStorageConfig, index_LocalStorage as LocalStorage, index_LocalStorageConfig as LocalStorageConfig, index_Manifest as Manifest, index_ManifestEntry as ManifestEntry, index_S3Storage as S3Storage, index_S3StorageConfig as S3StorageConfig, index_SHLAttachment as SHLAttachment, index_SHLMetadata as SHLMetadata, index_SHLOptions as SHLOptions, index_SHLResult as SHLResult, index_SHLStorage as SHLStorage, index_create as create, index_decode as decode, index_decrypt as decrypt, index_decryptContent as decryptContent, index_getEntryContent as getEntryContent, index_revoke as revoke };
|
|
874
876
|
}
|
|
875
877
|
|
|
876
878
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as SHLOptions, c as SHLResult, M as ManifestEntry, S as SHLStorage, d as
|
|
2
|
-
import { A as AzureStorage, a as AzureStorageConfig, F as FhirflyStorage, d as FhirflyStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, e as S3Storage, S as S3StorageConfig } from './storage-
|
|
1
|
+
import { b as SHLOptions, c as SHLResult, M as ManifestEntry, S as SHLStorage, E as EXPIRATION_PRESETS, d as ExpirationPreset, e as Manifest, f as SHLAttachment, a as SHLMetadata } from './types-CmeXnyth.js';
|
|
2
|
+
import { A as AzureStorage, a as AzureStorageConfig, F as FhirflyStorage, d as FhirflyStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, e as S3Storage, S as S3StorageConfig } from './storage-CvsOM1Eu.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shared FHIR R4 datatypes used across IPS bundle construction.
|
|
@@ -848,6 +848,8 @@ declare function revoke(shlId: string, storage: SHLStorage): Promise<void>;
|
|
|
848
848
|
declare const index_AzureStorage: typeof AzureStorage;
|
|
849
849
|
declare const index_AzureStorageConfig: typeof AzureStorageConfig;
|
|
850
850
|
type index_DecodedSHL = DecodedSHL;
|
|
851
|
+
declare const index_EXPIRATION_PRESETS: typeof EXPIRATION_PRESETS;
|
|
852
|
+
declare const index_ExpirationPreset: typeof ExpirationPreset;
|
|
851
853
|
declare const index_FhirflyStorage: typeof FhirflyStorage;
|
|
852
854
|
declare const index_FhirflyStorageConfig: typeof FhirflyStorageConfig;
|
|
853
855
|
declare const index_GCSStorage: typeof GCSStorage;
|
|
@@ -870,7 +872,7 @@ declare const index_decryptContent: typeof decryptContent;
|
|
|
870
872
|
declare const index_getEntryContent: typeof getEntryContent;
|
|
871
873
|
declare const index_revoke: typeof revoke;
|
|
872
874
|
declare namespace index {
|
|
873
|
-
export { index_AzureStorage as AzureStorage, index_AzureStorageConfig as AzureStorageConfig, type index_DecodedSHL as DecodedSHL, index_FhirflyStorage as FhirflyStorage, index_FhirflyStorageConfig as FhirflyStorageConfig, index_GCSStorage as GCSStorage, index_GCSStorageConfig as GCSStorageConfig, index_LocalStorage as LocalStorage, index_LocalStorageConfig as LocalStorageConfig, index_Manifest as Manifest, index_ManifestEntry as ManifestEntry, index_S3Storage as S3Storage, index_S3StorageConfig as S3StorageConfig, index_SHLAttachment as SHLAttachment, index_SHLMetadata as SHLMetadata, index_SHLOptions as SHLOptions, index_SHLResult as SHLResult, index_SHLStorage as SHLStorage, index_create as create, index_decode as decode, index_decrypt as decrypt, index_decryptContent as decryptContent, index_getEntryContent as getEntryContent, index_revoke as revoke };
|
|
875
|
+
export { index_AzureStorage as AzureStorage, index_AzureStorageConfig as AzureStorageConfig, type index_DecodedSHL as DecodedSHL, index_EXPIRATION_PRESETS as EXPIRATION_PRESETS, index_ExpirationPreset as ExpirationPreset, index_FhirflyStorage as FhirflyStorage, index_FhirflyStorageConfig as FhirflyStorageConfig, index_GCSStorage as GCSStorage, index_GCSStorageConfig as GCSStorageConfig, index_LocalStorage as LocalStorage, index_LocalStorageConfig as LocalStorageConfig, index_Manifest as Manifest, index_ManifestEntry as ManifestEntry, index_S3Storage as S3Storage, index_S3StorageConfig as S3StorageConfig, index_SHLAttachment as SHLAttachment, index_SHLMetadata as SHLMetadata, index_SHLOptions as SHLOptions, index_SHLResult as SHLResult, index_SHLStorage as SHLStorage, index_create as create, index_decode as decode, index_decrypt as decrypt, index_decryptContent as decryptContent, index_getEntryContent as getEntryContent, index_revoke as revoke };
|
|
874
876
|
}
|
|
875
877
|
|
|
876
878
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ips_exports as IPS, shl_exports as SHL } from './chunk-
|
|
1
|
+
export { ips_exports as IPS, shl_exports as SHL } from './chunk-YUMCDN7I.js';
|
|
2
2
|
export { EncryptionError, ShlError, StorageError, ValidationError } from './chunk-VKB3ESIV.js';
|
|
3
3
|
import './chunk-PZ5AY32C.js';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/dist/lambda.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkH37YQWF2_cjs = require('./chunk-H37YQWF2.cjs');
|
|
4
4
|
require('./chunk-Q7SFCCGT.cjs');
|
|
5
5
|
|
|
6
6
|
// src/adapters/lambda.ts
|
|
7
7
|
function lambdaHandler(config) {
|
|
8
|
-
const handle =
|
|
8
|
+
const handle = chunkH37YQWF2_cjs.createHandler(config);
|
|
9
9
|
const prefix = config.pathPrefix?.replace(/\/+$/, "") ?? "";
|
|
10
10
|
return async (event) => {
|
|
11
11
|
let path = event.requestContext.http.path;
|
package/dist/lambda.d.cts
CHANGED
package/dist/lambda.d.ts
CHANGED
package/dist/lambda.js
CHANGED
package/dist/server.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var chunkLXJB46WB_cjs = require('./chunk-LXJB46WB.cjs');
|
|
4
4
|
require('./chunk-UDS6UJAL.cjs');
|
|
5
|
-
var
|
|
5
|
+
var chunkH37YQWF2_cjs = require('./chunk-H37YQWF2.cjs');
|
|
6
6
|
require('./chunk-Q7SFCCGT.cjs');
|
|
7
7
|
|
|
8
8
|
|
|
@@ -25,7 +25,11 @@ Object.defineProperty(exports, "ServerS3Storage", {
|
|
|
25
25
|
});
|
|
26
26
|
Object.defineProperty(exports, "createHandler", {
|
|
27
27
|
enumerable: true,
|
|
28
|
-
get: function () { return
|
|
28
|
+
get: function () { return chunkH37YQWF2_cjs.createHandler; }
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(exports, "isAuditableStorage", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function () { return chunkH37YQWF2_cjs.isAuditableStorage; }
|
|
29
33
|
});
|
|
30
34
|
//# sourceMappingURL=server.cjs.map
|
|
31
35
|
//# sourceMappingURL=server.cjs.map
|
package/dist/server.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { S as SHLHandlerConfig, H as HandlerRequest, a as HandlerResponse, b as SHLServerStorage } from './types-
|
|
2
|
-
export { A as AccessEvent, C as CorsConfig } from './types-
|
|
3
|
-
import { A as AzureStorage, a as AzureStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, S as S3StorageConfig } from './storage-
|
|
4
|
-
import { a as SHLMetadata } from './types-
|
|
1
|
+
import { S as SHLHandlerConfig, H as HandlerRequest, a as HandlerResponse, b as SHLServerStorage } from './types-BcfxBDTA.cjs';
|
|
2
|
+
export { A as AccessEvent, c as AuditableStorage, C as CorsConfig, i as isAuditableStorage } from './types-BcfxBDTA.cjs';
|
|
3
|
+
import { A as AzureStorage, a as AzureStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, S as S3StorageConfig } from './storage-DggeMhOI.cjs';
|
|
4
|
+
import { a as SHLMetadata } from './types-CmeXnyth.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Create a framework-agnostic SHL request handler.
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { S as SHLHandlerConfig, H as HandlerRequest, a as HandlerResponse, b as SHLServerStorage } from './types
|
|
2
|
-
export { A as AccessEvent, C as CorsConfig } from './types
|
|
3
|
-
import { A as AzureStorage, a as AzureStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, S as S3StorageConfig } from './storage-
|
|
4
|
-
import { a as SHLMetadata } from './types-
|
|
1
|
+
import { S as SHLHandlerConfig, H as HandlerRequest, a as HandlerResponse, b as SHLServerStorage } from './types--SjcaaWT.js';
|
|
2
|
+
export { A as AccessEvent, c as AuditableStorage, C as CorsConfig, i as isAuditableStorage } from './types--SjcaaWT.js';
|
|
3
|
+
import { A as AzureStorage, a as AzureStorageConfig, G as GCSStorage, b as GCSStorageConfig, L as LocalStorage, c as LocalStorageConfig, S as S3StorageConfig } from './storage-CvsOM1Eu.js';
|
|
4
|
+
import { a as SHLMetadata } from './types-CmeXnyth.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Create a framework-agnostic SHL request handler.
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { ServerAzureStorage, ServerGCSStorage, ServerLocalStorage, ServerS3Storage } from './chunk-YV7ZD6OM.js';
|
|
2
2
|
import './chunk-VKB3ESIV.js';
|
|
3
|
-
export { createHandler } from './chunk-
|
|
3
|
+
export { createHandler, isAuditableStorage } from './chunk-IYRQRY4A.js';
|
|
4
4
|
import './chunk-PZ5AY32C.js';
|
|
5
5
|
//# sourceMappingURL=server.js.map
|
|
6
6
|
//# sourceMappingURL=server.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SHLStorage, a as SHLMetadata } from './types-
|
|
1
|
+
import { S as SHLStorage, a as SHLMetadata } from './types-CmeXnyth.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Framework-agnostic incoming request.
|
|
@@ -48,6 +48,24 @@ interface SHLServerStorage extends SHLStorage {
|
|
|
48
48
|
*/
|
|
49
49
|
updateMetadata(shlId: string, updater: (current: SHLMetadata) => SHLMetadata | null): Promise<SHLMetadata | null>;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Extended storage interface that includes audit logging.
|
|
53
|
+
*
|
|
54
|
+
* When a storage backend implements `AuditableStorage`, the server handler
|
|
55
|
+
* will automatically call `onAccess()` after each successful retrieval.
|
|
56
|
+
* This is in addition to the `onAccess` callback on `SHLHandlerConfig`.
|
|
57
|
+
*
|
|
58
|
+
* Existing `SHLServerStorage` implementations continue to work unchanged —
|
|
59
|
+
* audit logging is opt-in.
|
|
60
|
+
*/
|
|
61
|
+
interface AuditableStorage extends SHLServerStorage {
|
|
62
|
+
/** Called after each successful SHL retrieval. */
|
|
63
|
+
onAccess(shlId: string, event: AccessEvent): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Type guard: checks if a storage backend implements AuditableStorage.
|
|
67
|
+
*/
|
|
68
|
+
declare function isAuditableStorage(storage: SHLServerStorage): storage is AuditableStorage;
|
|
51
69
|
/**
|
|
52
70
|
* CORS configuration for the SHL server handler.
|
|
53
71
|
*
|
|
@@ -96,4 +114,4 @@ interface AccessEvent {
|
|
|
96
114
|
mode?: "manifest" | "direct";
|
|
97
115
|
}
|
|
98
116
|
|
|
99
|
-
export type
|
|
117
|
+
export { type AccessEvent as A, type CorsConfig as C, type HandlerRequest as H, type SHLHandlerConfig as S, type HandlerResponse as a, type SHLServerStorage as b, type AuditableStorage as c, isAuditableStorage as i };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SHLStorage, a as SHLMetadata } from './types-
|
|
1
|
+
import { S as SHLStorage, a as SHLMetadata } from './types-CmeXnyth.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Framework-agnostic incoming request.
|
|
@@ -48,6 +48,24 @@ interface SHLServerStorage extends SHLStorage {
|
|
|
48
48
|
*/
|
|
49
49
|
updateMetadata(shlId: string, updater: (current: SHLMetadata) => SHLMetadata | null): Promise<SHLMetadata | null>;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Extended storage interface that includes audit logging.
|
|
53
|
+
*
|
|
54
|
+
* When a storage backend implements `AuditableStorage`, the server handler
|
|
55
|
+
* will automatically call `onAccess()` after each successful retrieval.
|
|
56
|
+
* This is in addition to the `onAccess` callback on `SHLHandlerConfig`.
|
|
57
|
+
*
|
|
58
|
+
* Existing `SHLServerStorage` implementations continue to work unchanged —
|
|
59
|
+
* audit logging is opt-in.
|
|
60
|
+
*/
|
|
61
|
+
interface AuditableStorage extends SHLServerStorage {
|
|
62
|
+
/** Called after each successful SHL retrieval. */
|
|
63
|
+
onAccess(shlId: string, event: AccessEvent): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Type guard: checks if a storage backend implements AuditableStorage.
|
|
67
|
+
*/
|
|
68
|
+
declare function isAuditableStorage(storage: SHLServerStorage): storage is AuditableStorage;
|
|
51
69
|
/**
|
|
52
70
|
* CORS configuration for the SHL server handler.
|
|
53
71
|
*
|
|
@@ -96,4 +114,4 @@ interface AccessEvent {
|
|
|
96
114
|
mode?: "manifest" | "direct";
|
|
97
115
|
}
|
|
98
116
|
|
|
99
|
-
export type
|
|
117
|
+
export { type AccessEvent as A, type CorsConfig as C, type HandlerRequest as H, type SHLHandlerConfig as S, type HandlerResponse as a, type SHLServerStorage as b, type AuditableStorage as c, isAuditableStorage as i };
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named expiration presets for common SHL use cases.
|
|
3
|
+
*
|
|
4
|
+
* - `"point-of-care"` — 15 minutes (in-person sharing at a clinic or pharmacy)
|
|
5
|
+
* - `"appointment"` — 24 hours (share before a scheduled visit)
|
|
6
|
+
* - `"travel"` — 90 days (carry health records while traveling)
|
|
7
|
+
* - `"permanent"` — no expiration (long-lived link)
|
|
8
|
+
*/
|
|
9
|
+
type ExpirationPreset = "point-of-care" | "appointment" | "travel" | "permanent";
|
|
10
|
+
/** Duration in milliseconds for each expiration preset. 0 = no expiration. */
|
|
11
|
+
declare const EXPIRATION_PRESETS: Record<ExpirationPreset, number>;
|
|
1
12
|
/**
|
|
2
13
|
* Options for creating a SMART Health Link.
|
|
3
14
|
*/
|
|
@@ -8,8 +19,11 @@ interface SHLOptions {
|
|
|
8
19
|
attachments?: SHLAttachment[];
|
|
9
20
|
/** Optional passcode to protect the link */
|
|
10
21
|
passcode?: string;
|
|
11
|
-
/**
|
|
12
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Expiration for the link. Accepts a `Date` for an exact time,
|
|
24
|
+
* or an `ExpirationPreset` string for a named duration from now.
|
|
25
|
+
*/
|
|
26
|
+
expiresAt?: Date | ExpirationPreset;
|
|
13
27
|
/** Maximum number of times the link can be accessed */
|
|
14
28
|
maxAccesses?: number;
|
|
15
29
|
/** Label for the SHL (shown in viewer apps, max 80 chars) */
|
|
@@ -114,4 +128,4 @@ interface SHLStorage {
|
|
|
114
128
|
delete(prefix: string): Promise<void>;
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
export type
|
|
131
|
+
export { EXPIRATION_PRESETS as E, type ManifestEntry as M, type SHLStorage as S, type SHLMetadata as a, type SHLOptions as b, type SHLResult as c, type ExpirationPreset as d, type Manifest as e, type SHLAttachment as f };
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named expiration presets for common SHL use cases.
|
|
3
|
+
*
|
|
4
|
+
* - `"point-of-care"` — 15 minutes (in-person sharing at a clinic or pharmacy)
|
|
5
|
+
* - `"appointment"` — 24 hours (share before a scheduled visit)
|
|
6
|
+
* - `"travel"` — 90 days (carry health records while traveling)
|
|
7
|
+
* - `"permanent"` — no expiration (long-lived link)
|
|
8
|
+
*/
|
|
9
|
+
type ExpirationPreset = "point-of-care" | "appointment" | "travel" | "permanent";
|
|
10
|
+
/** Duration in milliseconds for each expiration preset. 0 = no expiration. */
|
|
11
|
+
declare const EXPIRATION_PRESETS: Record<ExpirationPreset, number>;
|
|
1
12
|
/**
|
|
2
13
|
* Options for creating a SMART Health Link.
|
|
3
14
|
*/
|
|
@@ -8,8 +19,11 @@ interface SHLOptions {
|
|
|
8
19
|
attachments?: SHLAttachment[];
|
|
9
20
|
/** Optional passcode to protect the link */
|
|
10
21
|
passcode?: string;
|
|
11
|
-
/**
|
|
12
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Expiration for the link. Accepts a `Date` for an exact time,
|
|
24
|
+
* or an `ExpirationPreset` string for a named duration from now.
|
|
25
|
+
*/
|
|
26
|
+
expiresAt?: Date | ExpirationPreset;
|
|
13
27
|
/** Maximum number of times the link can be accessed */
|
|
14
28
|
maxAccesses?: number;
|
|
15
29
|
/** Label for the SHL (shown in viewer apps, max 80 chars) */
|
|
@@ -114,4 +128,4 @@ interface SHLStorage {
|
|
|
114
128
|
delete(prefix: string): Promise<void>;
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
export type
|
|
131
|
+
export { EXPIRATION_PRESETS as E, type ManifestEntry as M, type SHLStorage as S, type SHLMetadata as a, type SHLOptions as b, type SHLResult as c, type ExpirationPreset as d, type Manifest as e, type SHLAttachment as f };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fhirfly-io/shl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Official FHIRfly SDK for SMART Health Links - IPS bundle creation and SHL sharing",
|
|
5
5
|
"author": "FHIRfly.io LLC <admin@fhirfly.io>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
"homepage": "https://fhirfly.io",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://
|
|
11
|
+
"url": "git+https://github.com/FHIRfly-io/fhirfly-shl.git"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://
|
|
14
|
+
"url": "https://github.com/FHIRfly-io/fhirfly-shl/issues"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"fhirfly",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/handler.ts"],"names":["createHash","timingSafeEqual"],"mappings":"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","file":"chunk-JOZ6XZPO.cjs","sourcesContent":["// Copyright 2026 FHIRfly.io LLC. All rights reserved.\n// Licensed under the MIT License. See LICENSE file in the project root.\nimport type {\n HandlerRequest,\n HandlerResponse,\n SHLHandlerConfig,\n CorsConfig,\n} from \"./types.js\";\nimport type { SHLMetadata, Manifest } from \"../shl/types.js\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Create a framework-agnostic SHL request handler.\n *\n * Returns an async function that processes incoming requests and returns\n * responses. This handler implements three routes:\n *\n * - `POST /{shlId}` — Manifest endpoint (validates passcode, checks access limits)\n * - `GET /{shlId}/content` — Content endpoint (serves encrypted JWE)\n * - `GET /{shlId}/attachment/{index}` — Attachment endpoint (serves encrypted attachment)\n *\n * By default, CORS headers are added to all responses so browser-based SHL\n * viewers can access self-hosted servers. Set `cors: false` to disable.\n *\n * Framework adapters (Express, Fastify, Lambda) translate their native\n * request/response types to/from `HandlerRequest`/`HandlerResponse`.\n *\n * @example\n * ```ts\n * const handle = createHandler({ storage });\n * const response = await handle({\n * method: \"POST\",\n * path: \"/abc123\",\n * body: { passcode: \"1234\" },\n * headers: { \"content-type\": \"application/json\" },\n * });\n * ```\n */\nexport function createHandler(\n config: SHLHandlerConfig,\n): (req: HandlerRequest) => Promise<HandlerResponse> {\n const { storage, onAccess } = config;\n const corsHeaders = resolveCorsHeaders(config.cors);\n\n return async (req: HandlerRequest): Promise<HandlerResponse> => {\n // Handle CORS preflight\n if (req.method === \"OPTIONS\") {\n return {\n status: 204,\n headers: { ...corsHeaders },\n body: \"\",\n };\n }\n\n // Normalize path: strip leading slash, split into segments\n const path = req.path.replace(/^\\/+/, \"\");\n const segments = path.split(\"/\").filter(Boolean);\n\n let response: HandlerResponse;\n\n // Route: POST /{shlId} → manifest\n if (segments.length === 1 && req.method === \"POST\") {\n response = await handleManifest(segments[0]!, req, storage, onAccess);\n }\n // Route: GET /{shlId} → direct access (flag U)\n else if (segments.length === 1 && req.method === \"GET\") {\n response = await handleDirectAccess(segments[0]!, req, storage, onAccess);\n }\n // Route: GET /{shlId}/content → serve encrypted content\n else if (segments.length === 2 && segments[1] === \"content\" && req.method === \"GET\") {\n response = await handleContent(segments[0]!, storage);\n }\n // Route: GET /{shlId}/attachment/{index} → serve encrypted attachment\n else if (segments.length === 3 && segments[1] === \"attachment\" && req.method === \"GET\") {\n response = await handleAttachment(segments[0]!, segments[2]!, storage);\n }\n // Method not allowed for known paths\n else if (segments.length === 2 && segments[1] === \"content\" && req.method !== \"GET\") {\n response = jsonResponse(405, { error: \"Method not allowed. Use GET for content requests.\" });\n }\n else if (segments.length === 3 && segments[1] === \"attachment\" && req.method !== \"GET\") {\n response = jsonResponse(405, { error: \"Method not allowed. Use GET for attachment requests.\" });\n }\n else {\n response = jsonResponse(404, { error: \"Not found\" });\n }\n\n // Inject CORS headers into every response\n response.headers = { ...response.headers, ...corsHeaders };\n return response;\n };\n}\n\n/** Resolve CORS headers from config. Returns empty object if disabled. */\nfunction resolveCorsHeaders(cors: SHLHandlerConfig[\"cors\"]): Record<string, string> {\n if (cors === false) return {};\n const c: CorsConfig = cors ?? {};\n return {\n \"access-control-allow-origin\": c.origin ?? \"*\",\n \"access-control-allow-methods\": c.methods ?? \"GET, POST, OPTIONS\",\n \"access-control-allow-headers\": c.headers ?? \"Content-Type, Authorization\",\n };\n}\n\nasync function handleManifest(\n shlId: string,\n req: HandlerRequest,\n storage: SHLHandlerConfig[\"storage\"],\n onAccess?: SHLHandlerConfig[\"onAccess\"],\n): Promise<HandlerResponse> {\n // Read manifest to verify the SHL exists\n const manifestRaw = await storage.read(`${shlId}/manifest.json`);\n if (manifestRaw === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Atomically check access control + increment counter\n let updatedMetadata: SHLMetadata | null = null;\n let accessDeniedReason: \"expired\" | \"exhausted\" | \"passcode\" | null = null;\n const reqBody = (req.body && typeof req.body === \"object\" ? req.body : {}) as Record<string, unknown>;\n const providedPasscode = typeof reqBody[\"passcode\"] === \"string\" ? reqBody[\"passcode\"] : undefined;\n\n updatedMetadata = await storage.updateMetadata(shlId, (metadata) => {\n // Check expiration\n if (metadata.expiresAt) {\n const expiresAt = new Date(metadata.expiresAt);\n if (expiresAt.getTime() <= Date.now()) {\n accessDeniedReason = \"expired\";\n return null;\n }\n }\n\n // Check access count\n const currentCount = metadata.accessCount ?? 0;\n if (metadata.maxAccesses !== undefined && currentCount >= metadata.maxAccesses) {\n accessDeniedReason = \"exhausted\";\n return null;\n }\n\n // Check passcode (timing-safe comparison with SHA-256 hash)\n if (metadata.passcode) {\n if (!providedPasscode) {\n accessDeniedReason = \"passcode\";\n return null;\n }\n const providedHash = createHash(\"sha256\").update(providedPasscode).digest(\"hex\");\n const storedHash = metadata.passcode;\n const a = Buffer.from(providedHash);\n const b = Buffer.from(storedHash);\n // Constant-time comparison: compare with self if lengths differ to avoid timing leak\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n accessDeniedReason = \"passcode\";\n return null;\n }\n }\n\n // Access granted — increment count\n return {\n ...metadata,\n accessCount: currentCount + 1,\n };\n });\n\n // Handle access control failures\n if (accessDeniedReason === \"expired\") {\n return jsonResponse(410, { error: \"SHL has expired\" });\n }\n if (accessDeniedReason === \"exhausted\") {\n return jsonResponse(410, { error: \"SHL access limit reached\" });\n }\n if (accessDeniedReason === \"passcode\") {\n return jsonResponse(401, { error: \"Invalid passcode\" });\n }\n\n // If updateMetadata returned null but no denied reason, metadata file is missing\n if (updatedMetadata === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Fire access event (non-blocking)\n if (onAccess) {\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const event = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"manifest\" as const,\n ...(recipient ? { recipient } : {}),\n };\n // Fire and forget — don't let callback errors break the response\n Promise.resolve(onAccess(event)).catch(() => {});\n }\n\n // Return manifest\n const manifestStr = typeof manifestRaw === \"string\"\n ? manifestRaw\n : new TextDecoder().decode(manifestRaw);\n const manifest = JSON.parse(manifestStr) as Manifest;\n\n return jsonResponse(200, manifest);\n}\n\nasync function handleDirectAccess(\n shlId: string,\n req: HandlerRequest,\n storage: SHLHandlerConfig[\"storage\"],\n onAccess?: SHLHandlerConfig[\"onAccess\"],\n): Promise<HandlerResponse> {\n // Read metadata to check if this is a direct-mode SHL\n const metadataRaw = await storage.read(`${shlId}/metadata.json`);\n if (metadataRaw === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n const metadataStr = typeof metadataRaw === \"string\" ? metadataRaw : new TextDecoder().decode(metadataRaw);\n const metadata = JSON.parse(metadataStr) as SHLMetadata;\n\n // Only direct-mode SHLs support GET retrieval\n if (metadata.mode !== \"direct\") {\n return jsonResponse(405, { error: \"Method not allowed. Use POST for manifest requests.\" });\n }\n\n // Access control: expiration, access count (atomic)\n let accessDeniedReason: \"expired\" | \"exhausted\" | null = null;\n const updatedMetadata = await storage.updateMetadata(shlId, (current) => {\n if (current.expiresAt) {\n const expiresAt = new Date(current.expiresAt);\n if (expiresAt.getTime() <= Date.now()) {\n accessDeniedReason = \"expired\";\n return null;\n }\n }\n\n const currentCount = current.accessCount ?? 0;\n if (current.maxAccesses !== undefined && currentCount >= current.maxAccesses) {\n accessDeniedReason = \"exhausted\";\n return null;\n }\n\n return {\n ...current,\n accessCount: currentCount + 1,\n };\n });\n\n if (accessDeniedReason === \"expired\") {\n return jsonResponse(410, { error: \"SHL has expired\" });\n }\n if (accessDeniedReason === \"exhausted\") {\n return jsonResponse(410, { error: \"SHL access limit reached\" });\n }\n if (updatedMetadata === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Fire access event (non-blocking)\n if (onAccess) {\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const event = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"direct\" as const,\n ...(recipient ? { recipient } : {}),\n };\n Promise.resolve(onAccess(event)).catch(() => {});\n }\n\n // Serve the encrypted content directly\n const content = await storage.read(`${shlId}/content.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Content not found\" });\n }\n\n const body = typeof content === \"string\" ? content : new TextDecoder().decode(content);\n\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nasync function handleContent(\n shlId: string,\n storage: SHLHandlerConfig[\"storage\"],\n): Promise<HandlerResponse> {\n const content = await storage.read(`${shlId}/content.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Content not found\" });\n }\n\n const body = typeof content === \"string\"\n ? content\n : new TextDecoder().decode(content);\n\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nasync function handleAttachment(\n shlId: string,\n index: string,\n storage: SHLHandlerConfig[\"storage\"],\n): Promise<HandlerResponse> {\n if (!/^\\d+$/.test(index)) {\n return jsonResponse(400, { error: \"Invalid attachment index\" });\n }\n const content = await storage.read(`${shlId}/attachment-${index}.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Attachment not found\" });\n }\n const body = typeof content === \"string\"\n ? content\n : new TextDecoder().decode(content);\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nfunction jsonResponse(status: number, body: unknown): HandlerResponse {\n return {\n status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n body: JSON.stringify(body),\n };\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/handler.ts"],"names":[],"mappings":"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","file":"chunk-KGEFZQ6W.js","sourcesContent":["// Copyright 2026 FHIRfly.io LLC. All rights reserved.\n// Licensed under the MIT License. See LICENSE file in the project root.\nimport type {\n HandlerRequest,\n HandlerResponse,\n SHLHandlerConfig,\n CorsConfig,\n} from \"./types.js\";\nimport type { SHLMetadata, Manifest } from \"../shl/types.js\";\nimport { createHash, timingSafeEqual } from \"node:crypto\";\n\n/**\n * Create a framework-agnostic SHL request handler.\n *\n * Returns an async function that processes incoming requests and returns\n * responses. This handler implements three routes:\n *\n * - `POST /{shlId}` — Manifest endpoint (validates passcode, checks access limits)\n * - `GET /{shlId}/content` — Content endpoint (serves encrypted JWE)\n * - `GET /{shlId}/attachment/{index}` — Attachment endpoint (serves encrypted attachment)\n *\n * By default, CORS headers are added to all responses so browser-based SHL\n * viewers can access self-hosted servers. Set `cors: false` to disable.\n *\n * Framework adapters (Express, Fastify, Lambda) translate their native\n * request/response types to/from `HandlerRequest`/`HandlerResponse`.\n *\n * @example\n * ```ts\n * const handle = createHandler({ storage });\n * const response = await handle({\n * method: \"POST\",\n * path: \"/abc123\",\n * body: { passcode: \"1234\" },\n * headers: { \"content-type\": \"application/json\" },\n * });\n * ```\n */\nexport function createHandler(\n config: SHLHandlerConfig,\n): (req: HandlerRequest) => Promise<HandlerResponse> {\n const { storage, onAccess } = config;\n const corsHeaders = resolveCorsHeaders(config.cors);\n\n return async (req: HandlerRequest): Promise<HandlerResponse> => {\n // Handle CORS preflight\n if (req.method === \"OPTIONS\") {\n return {\n status: 204,\n headers: { ...corsHeaders },\n body: \"\",\n };\n }\n\n // Normalize path: strip leading slash, split into segments\n const path = req.path.replace(/^\\/+/, \"\");\n const segments = path.split(\"/\").filter(Boolean);\n\n let response: HandlerResponse;\n\n // Route: POST /{shlId} → manifest\n if (segments.length === 1 && req.method === \"POST\") {\n response = await handleManifest(segments[0]!, req, storage, onAccess);\n }\n // Route: GET /{shlId} → direct access (flag U)\n else if (segments.length === 1 && req.method === \"GET\") {\n response = await handleDirectAccess(segments[0]!, req, storage, onAccess);\n }\n // Route: GET /{shlId}/content → serve encrypted content\n else if (segments.length === 2 && segments[1] === \"content\" && req.method === \"GET\") {\n response = await handleContent(segments[0]!, storage);\n }\n // Route: GET /{shlId}/attachment/{index} → serve encrypted attachment\n else if (segments.length === 3 && segments[1] === \"attachment\" && req.method === \"GET\") {\n response = await handleAttachment(segments[0]!, segments[2]!, storage);\n }\n // Method not allowed for known paths\n else if (segments.length === 2 && segments[1] === \"content\" && req.method !== \"GET\") {\n response = jsonResponse(405, { error: \"Method not allowed. Use GET for content requests.\" });\n }\n else if (segments.length === 3 && segments[1] === \"attachment\" && req.method !== \"GET\") {\n response = jsonResponse(405, { error: \"Method not allowed. Use GET for attachment requests.\" });\n }\n else {\n response = jsonResponse(404, { error: \"Not found\" });\n }\n\n // Inject CORS headers into every response\n response.headers = { ...response.headers, ...corsHeaders };\n return response;\n };\n}\n\n/** Resolve CORS headers from config. Returns empty object if disabled. */\nfunction resolveCorsHeaders(cors: SHLHandlerConfig[\"cors\"]): Record<string, string> {\n if (cors === false) return {};\n const c: CorsConfig = cors ?? {};\n return {\n \"access-control-allow-origin\": c.origin ?? \"*\",\n \"access-control-allow-methods\": c.methods ?? \"GET, POST, OPTIONS\",\n \"access-control-allow-headers\": c.headers ?? \"Content-Type, Authorization\",\n };\n}\n\nasync function handleManifest(\n shlId: string,\n req: HandlerRequest,\n storage: SHLHandlerConfig[\"storage\"],\n onAccess?: SHLHandlerConfig[\"onAccess\"],\n): Promise<HandlerResponse> {\n // Read manifest to verify the SHL exists\n const manifestRaw = await storage.read(`${shlId}/manifest.json`);\n if (manifestRaw === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Atomically check access control + increment counter\n let updatedMetadata: SHLMetadata | null = null;\n let accessDeniedReason: \"expired\" | \"exhausted\" | \"passcode\" | null = null;\n const reqBody = (req.body && typeof req.body === \"object\" ? req.body : {}) as Record<string, unknown>;\n const providedPasscode = typeof reqBody[\"passcode\"] === \"string\" ? reqBody[\"passcode\"] : undefined;\n\n updatedMetadata = await storage.updateMetadata(shlId, (metadata) => {\n // Check expiration\n if (metadata.expiresAt) {\n const expiresAt = new Date(metadata.expiresAt);\n if (expiresAt.getTime() <= Date.now()) {\n accessDeniedReason = \"expired\";\n return null;\n }\n }\n\n // Check access count\n const currentCount = metadata.accessCount ?? 0;\n if (metadata.maxAccesses !== undefined && currentCount >= metadata.maxAccesses) {\n accessDeniedReason = \"exhausted\";\n return null;\n }\n\n // Check passcode (timing-safe comparison with SHA-256 hash)\n if (metadata.passcode) {\n if (!providedPasscode) {\n accessDeniedReason = \"passcode\";\n return null;\n }\n const providedHash = createHash(\"sha256\").update(providedPasscode).digest(\"hex\");\n const storedHash = metadata.passcode;\n const a = Buffer.from(providedHash);\n const b = Buffer.from(storedHash);\n // Constant-time comparison: compare with self if lengths differ to avoid timing leak\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n accessDeniedReason = \"passcode\";\n return null;\n }\n }\n\n // Access granted — increment count\n return {\n ...metadata,\n accessCount: currentCount + 1,\n };\n });\n\n // Handle access control failures\n if (accessDeniedReason === \"expired\") {\n return jsonResponse(410, { error: \"SHL has expired\" });\n }\n if (accessDeniedReason === \"exhausted\") {\n return jsonResponse(410, { error: \"SHL access limit reached\" });\n }\n if (accessDeniedReason === \"passcode\") {\n return jsonResponse(401, { error: \"Invalid passcode\" });\n }\n\n // If updateMetadata returned null but no denied reason, metadata file is missing\n if (updatedMetadata === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Fire access event (non-blocking)\n if (onAccess) {\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const event = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"manifest\" as const,\n ...(recipient ? { recipient } : {}),\n };\n // Fire and forget — don't let callback errors break the response\n Promise.resolve(onAccess(event)).catch(() => {});\n }\n\n // Return manifest\n const manifestStr = typeof manifestRaw === \"string\"\n ? manifestRaw\n : new TextDecoder().decode(manifestRaw);\n const manifest = JSON.parse(manifestStr) as Manifest;\n\n return jsonResponse(200, manifest);\n}\n\nasync function handleDirectAccess(\n shlId: string,\n req: HandlerRequest,\n storage: SHLHandlerConfig[\"storage\"],\n onAccess?: SHLHandlerConfig[\"onAccess\"],\n): Promise<HandlerResponse> {\n // Read metadata to check if this is a direct-mode SHL\n const metadataRaw = await storage.read(`${shlId}/metadata.json`);\n if (metadataRaw === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n const metadataStr = typeof metadataRaw === \"string\" ? metadataRaw : new TextDecoder().decode(metadataRaw);\n const metadata = JSON.parse(metadataStr) as SHLMetadata;\n\n // Only direct-mode SHLs support GET retrieval\n if (metadata.mode !== \"direct\") {\n return jsonResponse(405, { error: \"Method not allowed. Use POST for manifest requests.\" });\n }\n\n // Access control: expiration, access count (atomic)\n let accessDeniedReason: \"expired\" | \"exhausted\" | null = null;\n const updatedMetadata = await storage.updateMetadata(shlId, (current) => {\n if (current.expiresAt) {\n const expiresAt = new Date(current.expiresAt);\n if (expiresAt.getTime() <= Date.now()) {\n accessDeniedReason = \"expired\";\n return null;\n }\n }\n\n const currentCount = current.accessCount ?? 0;\n if (current.maxAccesses !== undefined && currentCount >= current.maxAccesses) {\n accessDeniedReason = \"exhausted\";\n return null;\n }\n\n return {\n ...current,\n accessCount: currentCount + 1,\n };\n });\n\n if (accessDeniedReason === \"expired\") {\n return jsonResponse(410, { error: \"SHL has expired\" });\n }\n if (accessDeniedReason === \"exhausted\") {\n return jsonResponse(410, { error: \"SHL access limit reached\" });\n }\n if (updatedMetadata === null) {\n return jsonResponse(404, { error: \"SHL not found\" });\n }\n\n // Fire access event (non-blocking)\n if (onAccess) {\n const recipient = typeof req.query?.[\"recipient\"] === \"string\" ? req.query[\"recipient\"] : undefined;\n const event = {\n shlId,\n accessCount: updatedMetadata.accessCount ?? 1,\n timestamp: new Date(),\n mode: \"direct\" as const,\n ...(recipient ? { recipient } : {}),\n };\n Promise.resolve(onAccess(event)).catch(() => {});\n }\n\n // Serve the encrypted content directly\n const content = await storage.read(`${shlId}/content.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Content not found\" });\n }\n\n const body = typeof content === \"string\" ? content : new TextDecoder().decode(content);\n\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nasync function handleContent(\n shlId: string,\n storage: SHLHandlerConfig[\"storage\"],\n): Promise<HandlerResponse> {\n const content = await storage.read(`${shlId}/content.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Content not found\" });\n }\n\n const body = typeof content === \"string\"\n ? content\n : new TextDecoder().decode(content);\n\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nasync function handleAttachment(\n shlId: string,\n index: string,\n storage: SHLHandlerConfig[\"storage\"],\n): Promise<HandlerResponse> {\n if (!/^\\d+$/.test(index)) {\n return jsonResponse(400, { error: \"Invalid attachment index\" });\n }\n const content = await storage.read(`${shlId}/attachment-${index}.jwe`);\n if (content === null) {\n return jsonResponse(404, { error: \"Attachment not found\" });\n }\n const body = typeof content === \"string\"\n ? content\n : new TextDecoder().decode(content);\n return {\n status: 200,\n headers: {\n \"content-type\": \"application/jose\",\n \"cache-control\": \"no-store\",\n },\n body,\n };\n}\n\nfunction jsonResponse(status: number, body: unknown): HandlerResponse {\n return {\n status,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n body: JSON.stringify(body),\n };\n}\n"]}
|