import * as querystring from "querystring" ; import DataStoreManager from "../DataStoreManager" ; import DataStoreHttpRequest from "./DataStoreHttpRequest" ; import { DataStoreRequestType } from "../util/constants" ; import { checkKey , checkName , checkPlaceId , checkScope , checkValue } from "../util/checks" ; import { RESTResponseDataType } from "../../../../interfaces/RESTInterfaces" ; type DataStoreType = "OrderedDataStore" | "GlobalDataStore" ; type DataStoreKey = { Scope : "global" | string ; Target : string ; Key : string ; } ; type DataStoreValue = string ; export default class GenericDataStore < DataType extends any > { public manager : DataStoreManager ; public name : string ; public scope : string ; public legacy : boolean ; public baseAPIUrl : string ; public placeId : number ; public dataStoreType : DataStoreType ; public advanced : { parseData ? : ( data : string ) => DataType ; serializeData ? : ( data : DataType ) => string ; } ; constructor ( manager , dataStoreType , placeId , name , scope , legacy = false ) { this . manager = manager ; this . dataStoreType = dataStoreType ; this . placeId = placeId ; this . name = name ; this . scope = scope || "global" ; this . legacy = legacy ; this . baseAPIUrl = "https://gamepersistence.roblox.com/persistence/" ; this . advanced = { } ; this . performPreflightChecks ( { } ) ; } param { ( data : string ) => DataType } parseDataFunction param { ( data : DataType ) => string } serializeDataFunction public setDataConverters ( parseDataFunction , serializeDataFunction ) { this . advanced . parseData = parseDataFunction ; this . advanced . serializeData = serializeDataFunction ; } buildPostDataForKey ( key , index = 0 ) { const encodedQueryString = querystring . encode ( { [ `qkeys[${ index }].scope` ] : this . scope ? this . safeEncodeValue ( this . scope ) : "" , [ `qkeys[${ index }].target` ] : this . legacy ? "" : this . safeEncodeValue ( key ) , [ `qkeys[${ index }].key` ] : this . legacy ? this . safeEncodeValue ( key ) : this . safeEncodeValue ( this . name ) } ) ; return `&${ encodedQueryString }` ; } buildGetUrl ( ) { const encodedQueryString = this . createQueryString ( { } ) ; return `${ this . baseAPIUrl }getV2?${ encodedQueryString }` ; } buildSetUrl ( key , valueLength ) { const encodedQueryString = this . createQueryString ( { key : this . legacy ? this . safeEncodeValue ( key ) : this . safeEncodeValue ( this . name ) , target : this . legacy ? "" : this . safeEncodeValue ( key ) , valueLength } ) ; return `${ this . baseAPIUrl }set?${ encodedQueryString }` ; } buildSetIfUrl ( key , valueLength , expectedValueLength ) { const encodedQueryString = this . createQueryString ( { key : this . legacy ? this . safeEncodeValue ( key ) : this . safeEncodeValue ( this . name ) , target : this . legacy ? "" : this . safeEncodeValue ( key ) , valueLength , expectedValueLength } ) ; return `${ this . baseAPIUrl }set?${ encodedQueryString }` ; } buildIncrementUrl ( key , delta ) { const encodedQueryString = this . createQueryString ( { key : this . legacy ? this . safeEncodeValue ( key ) : this . safeEncodeValue ( this . name ) , target : this . legacy ? "" : this . safeEncodeValue ( key ) , value : delta } ) ; return `${ this . baseAPIUrl }increment?${ encodedQueryString }` ; } buildRemoveUrl ( key ) { const encodedQueryString = this . createQueryString ( { key : this . legacy ? this . safeEncodeValue ( key ) : this . safeEncodeValue ( this . name ) , target : this . legacy ? "" : this . safeEncodeValue ( key ) } ) ; return `${ this . baseAPIUrl }increment?${ encodedQueryString }` ; } public parseRetrievedData < Result extends any > ( data ) { let result = "" ; if ( data . length === 0 ) { return [ true , result ] ; } try { result = JSON . parse ( data ) ; } catch { return [ false , result ] ; } return [ true , result ] ; } param { string } key returns { Promise < unknown > } public async getAsync ( key ) { this . performPreflightChecks ( { key } ) ; const createdRequest = new DataStoreHttpRequest ( this . manager , { url : this . buildGetUrl ( ) , placeId : this . placeId , data : this . buildPostDataForKey ( key ) , requestType : DataStoreRequestType . GET_ASYNC } ) ; const response = await createdRequest . send ( ) ; const [ parsedResponseSuccess , parsedResponse ] = this . parseRetrievedData < { data : { Key : DataStoreKey ; Value : DataStoreValue ; } [ ] ; } > ( response . body ) ; if ( ! parsedResponseSuccess || ! parsedResponse ) { throw new Error ( `Failed to parse response from data stores!` ) ; } if ( parsedResponse . data && parsedResponse . data . length > 0 ) { return this . parseIncomingData ( parsedResponse . data [ 0 ] . Value ) ; } else { return null ; } } param { string } key param { DataType } value returns { Promise < boolean > } public async setAsync ( key , value ) { const serializedValue = this . serializeOutgoingData ( value ) ; this . performPreflightChecks ( { key , value : serializedValue } ) ; const createdRequest = new DataStoreHttpRequest ( this . manager , { url : this . buildSetUrl ( key , serializedValue . length ) , placeId : this . placeId , data : `value=${ this . safeEncodeValue ( serializedValue ) }` , requestType : DataStoreRequestType . SET_ASYNC } ) ; const response = await createdRequest . send ( ) ; const [ parsedResponseSuccess , parsedResponse ] = this . parseRetrievedData < { data : string ; } > ( response . body ) ; if ( ! parsedResponseSuccess || ! parsedResponse ) { throw new Error ( `Failed to parse response!` ) ; } return this . parseIncomingData ( parsedResponse . data as string ) ; } public incrementAsync ( key , delta = 1 ) { this . performPreflightChecks ( { key } ) ; const createdRequest = new DataStoreHttpRequest ( this . manager , { url : this . buildIncrementUrl ( key , delta ) , placeId : this . placeId , data : "" , requestType : DataStoreRequestType . INCREMENT_ASYNC } ) ; return createdRequest . send ( ) ; } public removeAsync ( key ) { this . performPreflightChecks ( { key } ) ; const createdRequest = new DataStoreHttpRequest ( this . manager , { url : this . buildRemoveUrl ( key ) , placeId : this . placeId , data : "" , requestType : DataStoreRequestType . SET_ASYNC } ) ; return createdRequest . send ( ) ; } public createQueryString ( addition ) { return querystring . encode ( { placeId : this . placeId , type : this . dataStoreType === "GlobalDataStore" ? "standard" : "sorted" , scope : this . safeEncodeValue ( this . scope ) , ... addition } ) ; } param { string } data returns { DataType } private parseIncomingData ( data ) { if ( this . advanced . parseData ) { return this . advanced . parseData ( data ) ; } else { return data as DataType ; } } param { DataType } data returns { string } private serializeOutgoingData ( data ) { let serializedStage1 = "" ; if ( this . advanced . serializeData ) { serializedStage1 = this . advanced . serializeData ( data ) ; } else { serializedStage1 = data ; } let serializedFinal = "" ; try { serializedFinal = JSON . stringify ( serializedStage1 ) ; } catch { serializedFinal = null ; } if ( ! serializedFinal ) { throw new Error ( `Failed to serialize the data using JSON.stringify` ) ; } return serializedFinal ; } safeEncodeValue ( input ) { return encodeURIComponent ( input ) ; } param { { key ? : string , value ? : DataType } } options private performPreflightChecks ( options ) { checkScope ( this . scope ) ; checkName ( this . name ) ; checkPlaceId ( this . placeId ) ; if ( options . key !== undefined ) { checkKey ( options . key ) ; } if ( options . value !== undefined ) { checkValue ( options . value ) ; } } }