import { assertKeetaStorageAnchorDeleteResponse, assertKeetaStorageAnchorPutResponse, assertKeetaStorageAnchorGetRequest, assertKeetaStorageAnchorSearchRequest, assertKeetaStorageAnchorSearchResponse, assertKeetaStorageAnchorQuotaResponse, assertKeetaStorageAnchorUpdateMetadataRequest } from './common.generated.js';
import { getKeetaStorageAnchorDeleteRequestSigningData, getKeetaStorageAnchorPutRequestSigningData, getKeetaStorageAnchorGetRequestSigningData, getKeetaStorageAnchorSearchRequestSigningData, getKeetaStorageAnchorQuotaRequestSigningData, getKeetaStorageAnchorUpdateMetadataRequestSigningData, parseContainerPayload, Errors, CONTENT_TYPE_OCTET_STREAM, DEFAULT_SIGNED_URL_TTL_SECONDS } from './common.js';
import { KeetaAnchorMetadataServer } from '../../lib/anchor-metadata-server.js';
import { VerifySignedData } from '../../lib/utils/signing.js';
import { parseSignatureFromURL, verifyBodyAuth, verifyURLAuth } from '../../lib/http-server/common.js';
import { arrayBufferLikeToBuffer, Buffer } from '../../lib/utils/buffer.js';
import { requiresValidation, findMatchingValidators } from './lib/validators.js';
import { EncryptedContainer, EncryptedContainerError } from '../../lib/encrypted-container.js';
import { assertVisibility } from './utils.js';
/**
 * Build a standardized search response from search results.
 */
function buildSearchResponse(results) {
    const response = {
        ok: true,
        results: results.results
    };
    if (results.nextCursor !== undefined) {
        response.nextCursor = results.nextCursor;
    }
    return (response);
}
// #region Module-Level Helpers
/**
 * Find a matching policy for a path, validate it, and check access.
 *
 * @param pathPolicies - Array of path policies to check against
 * @param account - The account to check access for
 * @param path - The path to check
 * @param operation - The operation being performed
 */
function assertPathAccess(pathPolicies, account, path, operation) {
    for (const policy of pathPolicies) {
        const parsed = policy.parse(path);
        if (parsed !== null) {
            policy.validate(path);
            if (!policy.checkAccess(account, parsed, operation)) {
                throw (new Errors.AccessDenied('Can only access your own namespace'));
            }
            return ({ policy, parsed });
        }
    }
    throw (new Errors.InvalidPath('Path does not match any policy'));
}
/**
 * Find a matching policy and parse a path.
 * Used for public endpoints where auth is optional.
 *
 * @param pathPolicies - Array of path policies to check against
 * @param path - The path to parse
 */
function parsePath(pathPolicies, path) {
    for (const policy of pathPolicies) {
        const parsed = policy.parse(path);
        if (parsed !== null) {
            policy.validate(path);
            return ({ policy, parsed });
        }
    }
    throw (new Errors.InvalidPath('Path does not match any policy'));
}
/**
 * Extract object path from wildcard route parameter.
 * Prepends a leading slash to create a valid storage path.
 *
 * @param params - Route parameters containing the wildcard match
 *
 * @returns The object path with leading slash
 *
 * @throws InvalidPath if wildcard parameter is missing
 */
function extractObjectPath(params) {
    const wildcardPath = params.get('**');
    if (!wildcardPath) {
        throw (new Errors.InvalidPath());
    }
    return ('/' + wildcardPath);
}
/**
 * Authorize access to an object path via URL-signed request.
 * Combines path validation, signature verification, and access control.
 *
 * @throws InvalidPath if path is invalid or doesn't match any policy
 * @throws AccessDenied if user doesn't have access to the path
 * @throws KeetaAnchorUserError if signature is invalid
 */
async function authorizeURLAccess(pathPolicies, params, url, operation, getSignable, certificateChain) {
    const objectPath = extractObjectPath(params);
    const { policy, parsed } = parsePath(pathPolicies, objectPath);
    const account = await verifyURLAuth(url, function (account) {
        return (getSignable(objectPath, account));
    }, certificateChain);
    if (!policy.checkAccess(account, parsed, operation)) {
        throw (new Errors.AccessDenied('Can only access your own namespace'));
    }
    return ({ account, objectPath, policy, parsed });
}
// Default quota configuration
const DEFAULT_QUOTAS = {
    maxObjectSize: 10 * 1024 * 1024, // 10MB
    maxObjectsPerUser: 1000,
    maxStoragePerUser: 100 * 1024 * 1024, // 100MB
    maxSearchLimit: 100,
    maxSignedUrlTTL: 86400 // 24 hours
};
// Default tag validation configuration
const DEFAULT_TAG_VALIDATION = {
    maxTags: 10,
    maxTagLength: 50,
    pattern: /^[a-zA-Z0-9_-]+$/
};
export class KeetaNetStorageAnchorHTTPServer extends KeetaAnchorMetadataServer {
    homepage;
    backend;
    anchorAccount;
    quotas;
    validators;
    signedUrlDefaultTTL;
    publicCorsOrigin;
    authenticatedCorsOrigin;
    pathPolicies;
    tagValidation;
    constructor(config) {
        super(config);
        this.homepage = config.homepage ?? '';
        this.backend = config.backend;
        this.anchorAccount = config.anchorAccount;
        this.quotas = { ...DEFAULT_QUOTAS, ...config.quotas };
        this.validators = config.validators ?? [];
        this.signedUrlDefaultTTL = config.signedUrlDefaultTTL ?? DEFAULT_SIGNED_URL_TTL_SECONDS;
        this.publicCorsOrigin = config.publicCORSOrigin ?? false;
        this.authenticatedCorsOrigin = config.authenticatedCORSOrigin ?? '*';
        this.pathPolicies = config.pathPolicies;
        this.tagValidation = {
            maxTags: config.tagValidation?.maxTags ?? DEFAULT_TAG_VALIDATION.maxTags,
            maxTagLength: config.tagValidation?.maxTagLength ?? DEFAULT_TAG_VALIDATION.maxTagLength,
            pattern: config.tagValidation?.pattern ?? DEFAULT_TAG_VALIDATION.pattern
        };
        // Validate anchorAccount has private key
        if (!this.anchorAccount.hasPrivateKey) {
            throw (new Error('anchorAccount must have a private key'));
        }
        // Validate at least one path policy is provided
        if (this.pathPolicies.length === 0) {
            throw (new Error('At least one path policy must be provided'));
        }
        // Validate quota configuration values are positive
        if (this.quotas.maxObjectSize <= 0) {
            throw (new Error('quotas.maxObjectSize must be positive'));
        }
        if (this.quotas.maxObjectsPerUser <= 0) {
            throw (new Error('quotas.maxObjectsPerUser must be positive'));
        }
        if (this.quotas.maxStoragePerUser <= 0) {
            throw (new Error('quotas.maxStoragePerUser must be positive'));
        }
        if (this.quotas.maxSearchLimit <= 0) {
            throw (new Error('quotas.maxSearchLimit must be positive'));
        }
        if (this.quotas.maxSignedUrlTTL <= 0) {
            throw (new Error('quotas.maxSignedUrlTTL must be positive'));
        }
        // Validate tag validation configuration
        if (this.tagValidation.maxTags <= 0) {
            throw (new Error('tagValidation.maxTags must be positive'));
        }
        if (this.tagValidation.maxTagLength <= 0) {
            throw (new Error('tagValidation.maxTagLength must be positive'));
        }
    }
    // Note: We use this.* properties instead of config.*.
    // The config parameter is required by the abstract method signature but unused here.
    async initRoutes(_ignoreConfig) {
        const routes = {};
        const backend = this.backend;
        const anchorAccount = this.anchorAccount;
        const quotas = this.quotas;
        const validators = this.validators;
        const publicCorsOrigin = this.publicCorsOrigin;
        const authenticatedCorsOrigin = this.authenticatedCorsOrigin;
        const pathPolicies = this.pathPolicies;
        const requireCertificateChain = this.resolvedCertificateChainRequirement;
        const tagValidation = this.tagValidation;
        const logger = this.logger;
        /**
         * Validate tags against configured limits.
         * @throws Errors.InvalidTag if any tag violates constraints
         */
        function validateTags(tags) {
            const { maxTags, maxTagLength, pattern: tagPattern } = tagValidation;
            for (const tag of tags) {
                if (tag.length > maxTagLength) {
                    throw (new Errors.InvalidTag(`Tag exceeds maximum length of ${maxTagLength}: "${tag}"`));
                }
                tagPattern.lastIndex = 0;
                if (!tagPattern.test(tag)) {
                    throw (new Errors.InvalidTag(`Tag contains invalid characters: "${tag}"`));
                }
            }
            if (tags.length > maxTags) {
                throw (new Errors.InvalidTag(`Too many tags: ${tags.length} exceeds maximum of ${maxTags}`));
            }
        }
        /**
         * Build a JSON response with assertion.
         */
        function jsonResponse(response, assertionHandler) {
            return ({ output: JSON.stringify(assertionHandler(response)) });
        }
        /**
         * Get an object or throw DocumentNotFound.
         */
        async function requireObject(path) {
            const result = await backend.get(path);
            if (!result) {
                throw (new Errors.DocumentNotFound());
            }
            return (result);
        }
        /**
         * Enforce server-side search limit cap.
         */
        function enforceSearchLimit(pagination) {
            const requestedLimit = pagination?.limit ?? quotas.maxSearchLimit;
            return ({ ...pagination, limit: Math.min(requestedLimit, quotas.maxSearchLimit) });
        }
        /**
         * Validate search results match expected constraints.
         */
        function assertSearchResults(results, constraint) {
            for (const obj of results.results) {
                if (constraint.visibility && obj.visibility !== constraint.visibility) {
                    throw (new Errors.InvariantViolation(`Backend returned ${obj.visibility} object in ${constraint.visibility} search`));
                }
                if (constraint.owner && obj.owner !== constraint.owner) {
                    throw (new Errors.InvariantViolation(`Backend returned object owned by ${obj.owner} in search for ${constraint.owner}`));
                }
            }
        }
        /**
         * If a homepage is provided, setup the route for it
         */
        const homepage = this.homepage;
        if (homepage) {
            routes['GET /'] = async function () {
                let homepageData;
                if (typeof homepage === 'string') {
                    homepageData = homepage;
                }
                else {
                    homepageData = await homepage();
                }
                return ({
                    output: homepageData,
                    contentType: 'text/html'
                });
            };
        }
        // #region API Routes
        // PUT /api/object/* - Create or update an object
        routes['PUT /api/object/**'] = {
            bodyType: 'raw',
            maxBodySize: quotas.maxObjectSize,
            handler: async function (params, postData, _headers, url) {
                const objectPath = extractObjectPath(params);
                // Get metadata from query params
                const parsedUrl = new URL(url);
                const visibilityParam = parsedUrl.searchParams.get('visibility');
                const tagsParam = parsedUrl.searchParams.get('tags');
                // Default to private when absent, assert valid value otherwise
                let visibility = 'private';
                if (visibilityParam !== null) {
                    visibility = assertVisibility(visibilityParam);
                }
                const rawTags = (tagsParam ?? '')
                    .split(',')
                    .map(function (t) {
                    return (t.trim());
                })
                    .filter(function (t) {
                    return (t.length > 0);
                });
                // Verify signature
                const account = await verifyURLAuth(url, function () {
                    return (getKeetaStorageAnchorPutRequestSigningData({ path: objectPath, visibility, tags: rawTags }));
                }, requireCertificateChain);
                validateTags(rawTags);
                const tags = rawTags;
                // Validate path format, metadata, and ownership
                const { policy, parsed } = assertPathAccess(pathPolicies, account, objectPath, 'put');
                const owner = policy.getOwner(objectPath);
                // Resolve per-user quota limits, falling back to global config
                let userLimits = null;
                if (backend.getQuotaLimits) {
                    userLimits = await backend.getQuotaLimits(owner);
                }
                const effectiveLimits = userLimits ?? quotas;
                // Body is raw binary (EncryptedContainer)
                const data = arrayBufferLikeToBuffer(postData);
                const objectSize = data.byteLength;
                if (objectSize > effectiveLimits.maxObjectSize) {
                    throw (new Errors.QuotaExceeded({
                        quotaType: 'maxObjectSize',
                        limit: effectiveLimits.maxObjectSize,
                        current: objectSize
                    }));
                }
                const needsValidation = requiresValidation(objectPath, validators);
                const needsContextValidation = !!policy.validateContext;
                const needsAnchorDecryption = needsValidation || visibility === 'public';
                if (needsContextValidation || needsAnchorDecryption) {
                    try {
                        const container = EncryptedContainer.fromEncryptedBuffer(data, [anchorAccount]);
                        if (policy.validateContext) {
                            policy.validateContext(parsed, { operation: 'put', account, metadata: { owner, tags, visibility }, container });
                        }
                        if (needsAnchorDecryption) {
                            const plaintext = await container.getPlaintext();
                            if (needsValidation) {
                                // Extract content and mimeType from encrypted payload
                                const { content, mimeType } = parseContainerPayload(plaintext);
                                const matchingValidators = findMatchingValidators(objectPath, validators);
                                for (const validator of matchingValidators) {
                                    const result = await validator.validate(objectPath, content, mimeType);
                                    if (!result.valid) {
                                        throw (new Errors.ValidationFailed(result.error));
                                    }
                                }
                            }
                        }
                    }
                    catch (e) {
                        if (Errors.InvalidMetadata.isInstance(e)) {
                            throw (e);
                        }
                        if (Errors.ValidationFailed.isInstance(e)) {
                            throw (e);
                        }
                        if (EncryptedContainerError.isInstance(e)) {
                            if (e.code.startsWith('MALFORMED_')) {
                                throw (new Errors.ValidationFailed(`Invalid encrypted container: ${e.message}`));
                            }
                            if (e.code === 'NO_MATCHING_KEY' || e.code === 'DECRYPTION_FAILED') {
                                throw (new Errors.AnchorPrincipalRequired());
                            }
                        }
                        throw (e);
                    }
                }
                // Reserve quota before upload
                const reservation = await backend.reserveUpload(owner, objectPath, objectSize, {
                    quotaLimits: {
                        maxObjectsPerUser: effectiveLimits.maxObjectsPerUser,
                        maxStoragePerUser: effectiveLimits.maxStoragePerUser
                    }
                });
                let objectMetadata;
                try {
                    objectMetadata = await backend.put(objectPath, data, {
                        owner,
                        tags,
                        visibility
                    });
                    await backend.commitUpload(reservation.id);
                }
                catch (e) {
                    try {
                        await backend.releaseUpload(reservation.id);
                    }
                    catch (releaseError) {
                        /**
                         * This provides a hint for cleanup
                         */
                        logger?.warn('Failed to release upload reservation', { reservationId: reservation.id, error: releaseError });
                    }
                    throw (e);
                }
                const response = {
                    ok: true,
                    object: objectMetadata
                };
                return (jsonResponse(response, assertKeetaStorageAnchorPutResponse));
            }
        };
        // GET /api/object/* - Retrieve an object
        routes['GET /api/object/**'] = async function (params, _postData, _headers, url) {
            const { objectPath, policy, parsed, account } = await authorizeURLAccess(pathPolicies, params, url, 'get', function (path, signingAccount) {
                return (getKeetaStorageAnchorGetRequestSigningData(assertKeetaStorageAnchorGetRequest({ path, account: signingAccount.publicKeyString.get() })));
            }, requireCertificateChain);
            if (policy.validateContext) {
                policy.validateContext(parsed, { operation: 'get', account });
            }
            const headers = {};
            if (authenticatedCorsOrigin) {
                headers['Access-Control-Allow-Origin'] = authenticatedCorsOrigin;
            }
            const result = await requireObject(objectPath);
            return ({
                headers,
                output: result.data,
                contentType: CONTENT_TYPE_OCTET_STREAM
            });
        };
        // DELETE /api/object/* - Delete an object
        routes['DELETE /api/object/**'] = async function (params, _postData, _headers, url) {
            const { objectPath, policy, parsed, account } = await authorizeURLAccess(pathPolicies, params, url, 'delete', function (path, signingAccount) {
                return (getKeetaStorageAnchorDeleteRequestSigningData({ path, account: signingAccount.publicKeyString.get() }));
            }, requireCertificateChain);
            if (policy.validateContext) {
                policy.validateContext(parsed, { operation: 'delete', account });
            }
            const deleted = await backend.delete(objectPath);
            const response = {
                ok: true,
                deleted
            };
            return (jsonResponse(response, assertKeetaStorageAnchorDeleteResponse));
        };
        // GET /api/metadata/* - Get object metadata
        routes['GET /api/metadata/**'] = async function (params, _postData, _headers, url) {
            const { objectPath, policy, parsed, account } = await authorizeURLAccess(pathPolicies, params, url, 'metadata', function (path, signingAccount) {
                return (getKeetaStorageAnchorGetRequestSigningData(assertKeetaStorageAnchorGetRequest({ path, account: signingAccount.publicKeyString.get() })));
            }, requireCertificateChain);
            if (policy.validateContext) {
                policy.validateContext(parsed, { operation: 'metadata', account });
            }
            const result = await requireObject(objectPath);
            return (jsonResponse({ ok: true, object: result.metadata }, assertKeetaStorageAnchorPutResponse));
        };
        // PUT /api/metadata/* - Update object metadata without re-uploading data
        routes['PUT /api/metadata/**'] = async function (params, postData) {
            const objectPath = extractObjectPath(params);
            const request = assertKeetaStorageAnchorUpdateMetadataRequest(postData);
            const signable = getKeetaStorageAnchorUpdateMetadataRequestSigningData({
                path: objectPath,
                visibility: request.visibility,
                tags: request.tags
            });
            const account = await verifyBodyAuth(request, function () {
                return (signable);
            }, requireCertificateChain);
            const { policy, parsed } = assertPathAccess(pathPolicies, account, objectPath, 'updateMetadata');
            validateTags(request.tags);
            const visibility = assertVisibility(request.visibility);
            const tags = request.tags;
            // Confirm object exists and preserve existing owner
            const existing = await requireObject(objectPath);
            if (policy.validateContext) {
                policy.validateContext(parsed, { operation: 'updateMetadata', account, metadata: { owner: existing.metadata.owner, tags, visibility }, current: existing.metadata });
            }
            if (!backend.updateMetadata) {
                throw (new Errors.OperationNotSupported('updateMetadata'));
            }
            const updated = await backend.updateMetadata(objectPath, { tags, visibility });
            if (!updated) {
                throw (new Errors.DocumentNotFound());
            }
            return (jsonResponse({ ok: true, object: updated }, assertKeetaStorageAnchorPutResponse));
        };
        // POST /api/search - Search for objects
        routes['POST /api/search'] = async function (_params, postData) {
            const request = assertKeetaStorageAnchorSearchRequest(postData);
            const account = await verifyBodyAuth(request, getKeetaStorageAnchorSearchRequestSigningData, requireCertificateChain);
            const accountPubKey = account.publicKeyString.get();
            // Check if searching for public objects outside namespace
            const searchingPublic = request.criteria.visibility === 'public';
            if (searchingPublic) {
                // When searching for public objects, we allow searching outside the caller's namespace
                // but only for objects with visibility: 'public'
                const scopedCriteria = {
                    ...request.criteria,
                    visibility: 'public'
                };
                const results = await backend.search(scopedCriteria, enforceSearchLimit(request.pagination));
                assertSearchResults(results, { visibility: 'public' });
                return (jsonResponse(buildSearchResponse(results), assertKeetaStorageAnchorSearchResponse));
            }
            // Scope search to authenticated account's namespace
            const scopedCriteria = {
                ...request.criteria,
                owner: accountPubKey
            };
            const results = await backend.search(scopedCriteria, enforceSearchLimit(request.pagination));
            assertSearchResults(results, { owner: accountPubKey });
            return (jsonResponse(buildSearchResponse(results), assertKeetaStorageAnchorSearchResponse));
        };
        // GET /api/quota - Get quota status
        routes['GET /api/quota'] = async function (_params, _postData, _headers, url) {
            const account = await verifyURLAuth(url, function () { return (getKeetaStorageAnchorQuotaRequestSigningData({})); }, requireCertificateChain);
            // Get current usage from backend and compute remaining using per-user or global limits
            const owner = account.publicKeyString.get();
            let userLimits = null;
            if (backend.getQuotaLimits) {
                userLimits = await backend.getQuotaLimits(owner);
            }
            const effectiveLimits = userLimits ?? quotas;
            const backendStatus = await backend.getQuotaStatus(owner);
            // Compute remaining from config limits
            let remainingObjects = Math.max(0, effectiveLimits.maxObjectsPerUser - backendStatus.objectCount);
            let remainingSize = Math.max(0, effectiveLimits.maxStoragePerUser - backendStatus.totalSize);
            // If backend reports its own remaining values, use the tighter constraint
            if (backendStatus.remainingObjects !== undefined && backendStatus.remainingObjects > 0) {
                remainingObjects = Math.min(backendStatus.remainingObjects, remainingObjects);
            }
            if (backendStatus.remainingSize !== undefined && backendStatus.remainingSize > 0) {
                remainingSize = Math.min(backendStatus.remainingSize, remainingSize);
            }
            const response = {
                ok: true,
                quota: {
                    objectCount: backendStatus.objectCount,
                    totalSize: backendStatus.totalSize,
                    remainingObjects,
                    remainingSize
                }
            };
            return (jsonResponse(response, assertKeetaStorageAnchorQuotaResponse));
        };
        // GET /api/public/** - Public object access via pre-signed URL
        routes['GET /api/public/**'] = async function (params, _postData, _headers, url) {
            const objectPath = extractObjectPath(params);
            const { policy, parsed } = parsePath(pathPolicies, objectPath);
            // Parse signature using standard signed field convention
            const urlParsed = parseSignatureFromURL(url);
            if (!urlParsed.signedField) {
                throw (new Errors.SignatureInvalid('Missing required signature parameters'));
            }
            // Resolve signer: policy-specified or from URL account param (any-signer for public objects)
            const signerAccount = policy.getAuthorizedSigner(parsed) ?? urlParsed.account ?? null;
            if (!signerAccount) {
                throw (new Errors.SignatureInvalid('Missing signer'));
            }
            const signerPubKey = signerAccount.publicKeyString.get();
            // Parse and validate expires param
            const parsedUrl = typeof url === 'string' ? new URL(url) : url;
            const expiresParam = parsedUrl.searchParams.get('expires');
            if (!expiresParam) {
                throw (new Errors.SignatureInvalid('Missing expires parameter'));
            }
            const expiresAt = parseInt(expiresParam, 10);
            if (!Number.isFinite(expiresAt)) {
                throw (new Errors.SignatureInvalid('Invalid expires parameter'));
            }
            if (Date.now() > expiresAt * 1000) {
                throw (new Errors.SignatureExpired());
            }
            // Enforce maximum TTL
            const maxExpiresAt = Math.floor(Date.now() / 1000) + quotas.maxSignedUrlTTL;
            if (expiresAt > maxExpiresAt) {
                throw (new Errors.SignatureExpired('Signed URL TTL exceeds maximum allowed'));
            }
            // Pre-validate signature is valid base64 with reasonable length
            const signatureBuffer = Buffer.from(urlParsed.signedField.signature, 'base64');
            if (signatureBuffer.length < 64 || signatureBuffer.length > 256) {
                throw (new Errors.SignatureInvalid('Invalid signature format'));
            }
            try {
                // Allow 5 minutes of clock skew for signature verification
                const valid = await VerifySignedData(signerAccount, [objectPath, expiresAt, signerPubKey], urlParsed.signedField, {
                    maxSkewMs: 5 * 60 * 1000
                });
                if (!valid) {
                    throw (new Errors.SignatureInvalid());
                }
            }
            catch (e) {
                if (Errors.SignatureInvalid.isInstance(e)) {
                    throw (e);
                }
                throw (new Errors.SignatureInvalid('Signature verification failed'));
            }
            if (policy.validateContext) {
                policy.validateContext(parsed, { operation: 'get', account: signerAccount });
            }
            const result = await requireObject(objectPath);
            if (result.metadata.visibility !== 'public') {
                throw (new Errors.AccessDenied('Object is not public'));
            }
            // Decrypt using anchor account and extract mimeType from encrypted payload
            const data = arrayBufferLikeToBuffer(result.data);
            const container = EncryptedContainer.fromEncryptedBuffer(data, [anchorAccount]);
            const plaintext = await container.getPlaintext();
            const { content, mimeType } = parseContainerPayload(plaintext);
            const headers = {};
            if (publicCorsOrigin) {
                headers['Access-Control-Allow-Origin'] = publicCorsOrigin;
            }
            return ({
                output: content,
                contentType: mimeType,
                headers
            });
        };
        // #endregion
        return (routes);
    }
    async buildServiceMetadata() {
        const authRequired = { options: { authentication: { type: 'required', method: 'keeta-account' } } };
        const acceptedIssuerDNs = this.acceptedIssuerDNs();
        const operations = {
            put: { url: (new URL('/api/object', this.url)).toString(), ...authRequired },
            get: { url: (new URL('/api/object', this.url)).toString(), ...authRequired },
            delete: { url: (new URL('/api/object', this.url)).toString(), ...authRequired },
            metadata: { url: (new URL('/api/metadata', this.url)).toString(), ...authRequired },
            updateMetadata: { url: (new URL('/api/metadata', this.url)).toString(), ...authRequired },
            search: { url: (new URL('/api/search', this.url)).toString(), ...authRequired },
            public: (new URL('/api/public', this.url)).toString(),
            quota: { url: (new URL('/api/quota', this.url)).toString(), ...authRequired }
        };
        return ({
            operations,
            anchorAccount: this.anchorAccount.publicKeyString.get(),
            quotas: this.quotas,
            signedUrlDefaultTTL: this.signedUrlDefaultTTL,
            searchableFields: ['owner', 'tags', 'visibility', 'pathPrefix'],
            ...(acceptedIssuerDNs !== undefined && { acceptedIssuerDNs })
        });
    }
}
//# sourceMappingURL=server.js.map