import { createHash , webcrypto } from "crypto" ; import { URL } from "url" ; import { Request , RequestInfo , RequestInit , Response , _buildUnknownProtocolWarning , _urlFromRequestInput , withImmutableHeaders , withInputGating , } from "@miniflare/core" ; import { Awaitable , Context , InputGate , OutputGate , PluginContext , RequestContext , getRequestContext , } from "@miniflare/shared" ; import { Response as BaseResponse } from "undici" ; import { DurableObjectError } from "./error" ; import { DurableObjectStorage } from "./storage" ; function hexEncode ( value ) { return Array . from ( value ) . map ( ( byte ) => byte . toString ( 16 ) . padStart ( 2 , "0" ) ) . join ( "" ) ; } export const kObjectName = Symbol ( "kObjectName" ) ; export class DurableObjectId { readonly [ kObjectName ] : string ; readonly #hexId : string ; constructor ( objectName , hexId , readonly name ? ) { this [ kObjectName ] = objectName ; this . #hexId = hexId ; } equals ( other ) { if ( ! ( other instanceof DurableObjectId ) ) return false ; return this . #hexId === other . #hexId ; } toString ( ) { return this . #hexId ; } } export interface DurableObjectConstructor { new ( state , env ) : DurableObject ; } export interface DurableObject { fetch ( request ) : Awaitable < Response > ; } export const kInstance = Symbol ( "kInstance" ) ; const kFetch = Symbol ( "kFetch" ) ; export class DurableObjectState { #inputGate = new InputGate ( ) ; [ kInstance ] ? : DurableObject ; constructor ( readonly id , readonly storage ) { } waitUntil ( _promise ) { } blockConcurrencyWhile < T > ( closure ) { return this . #inputGate . runWithClosed ( closure ) ; } [ kFetch ] ( request ) { const outputGate = new OutputGate ( ) ; return outputGate . runWith ( ( ) => this . #inputGate . runWith ( ( ) => this [ kInstance ] ! . fetch ( request ) ) ) ; } } export type DurableObjectFactory = ( id : DurableObjectId ) => Promise < DurableObjectState > ; export class DurableObjectStub { readonly #factory : DurableObjectFactory ; readonly #ctx ? : PluginContext ; constructor ( factory , readonly id , ctx ? ) { this . #factory = factory ; this . #ctx = ctx ; } get name ( ) : string | undefined { return this . id . name ; } async fetch ( input , init ? ) { const state = await this . #factory ( this . id ) ; if ( ! this . #ctx ?. compat . isEnabled ( "durable_object_fetch_requires_full_url" ) && typeof input === "string" ) { input = new URL ( input , "https://fake-host" ) ; } const url = _urlFromRequestInput ( input ) ; if ( url . protocol !== "http:" && url . protocol !== "https:" ) { if ( this . #ctx ?. compat . isEnabled ( "fetch_refuses_unknown_protocols" ) ) { throw new TypeError ( `Fetch API cannot load: ${ url . toString ( ) }` ) ; } else { this . #ctx ?. log . warn ( _buildUnknownProtocolWarning ( url ) ) ; } } const req = input instanceof Request && ! init ? input : new Request ( input , init ) ; const parentContext = getRequestContext ( ) ; const requestDepth = ( parentContext ?. requestDepth ?? 0 ) + 1 ; const res = await new RequestContext ( { requestDepth , pipelineDepth : 1 , durableObject : true , } ) . runWith ( ( ) => state [ kFetch ] ( withInputGating ( withImmutableHeaders ( req ) ) ) ) ; const validRes = res instanceof Response || ( res as any ) instanceof BaseResponse ; if ( ! validRes ) { throw new DurableObjectError ( "ERR_RESPONSE_TYPE" , "Durable Object fetch handler didn't respond with a Response object" ) ; } return res ; } } export interface NewUniqueIdOptions { jurisdiction ? : string ; } const HEX_ID_REGEXP = /^[A-Za-z0-9]{64}$/ ; export class DurableObjectNamespace { readonly #objectName : string ; readonly #factory : DurableObjectFactory ; readonly #objectNameHash : Uint8Array ; readonly #objectNameHashHex : string ; readonly #ctx ? : PluginContext ; constructor ( objectName , factory , ctx ? ) { this . #objectName = objectName ; this . #factory = factory ; this . #objectNameHash = createHash ( "sha256" ) . update ( this . #objectName ) . digest ( ) . slice ( 0 , 8 ) ; this . #objectNameHashHex = hexEncode ( this . #objectNameHash ) ; this . #ctx = ctx ; } newUniqueId ( _options ? ) { const id = new Uint8Array ( 32 ) ; const view = new DataView ( id . buffer ) ; view . setBigUint64 ( 1 , BigInt ( Date . now ( ) ) ) ; webcrypto . getRandomValues ( new DataView ( id . buffer , 9 , 15 ) ) ; id . set ( this . #objectNameHash , 24 ) ; return new DurableObjectId ( this . #objectName , hexEncode ( id ) ) ; } idFromName ( name ) { const id = createHash ( "sha256" ) . update ( this . #objectName ) . update ( name ) . digest ( ) ; id [ 0 ] |= 128 ; id . set ( this . #objectNameHash , 24 ) ; return new DurableObjectId ( this . #objectName , hexEncode ( id ) , name ) ; } idFromString ( hexId ) { if ( ! HEX_ID_REGEXP . test ( hexId ) ) { throw new TypeError ( "Invalid Durable Object ID. Durable Object IDs must be 64 hex digits." ) ; } if ( ! hexId . endsWith ( this . #objectNameHashHex ) ) { throw new TypeError ( "Invalid Durable Object ID. The ID does not match this Durable Object class." ) ; } return new DurableObjectId ( this . #objectName , hexId . toLowerCase ( ) ) ; } get ( id ) { if ( id [ kObjectName ] !== this . #objectName || ! id . toString ( ) . endsWith ( this . #objectNameHashHex ) ) { throw new TypeError ( "ID is not for this Durable Object class." ) ; } return new DurableObjectStub ( this . #factory , id , this . #ctx ) ; } }