Skip to content

Commit 312ac1a

Browse files
authored
Merge pull request #526 from coderoad/fallback-to-file
fallback session state to file
2 parents d01b0d2 + ba4e1ac commit 312ac1a

File tree

7 files changed

+77
-8
lines changed

7 files changed

+77
-8
lines changed

docs/docs/env-vars.md

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ CodeRoad has a number of configurations:
2020

2121
-`CODEROAD_WEBHOOK_TOKEN` - an optional token for authenticating/authorizing webhook endpoints. Passed to the webhook endpoint in a `CodeRoad-User-Token` header.
2222

23+
-`CODEROAD_SESSION_STORAGE_PATH` - the path to a directory for writing session storage to files. Helps preserves state across containers. Example: `../tmp`.
24+
2325
## How to Use Variables
2426

2527
### Local

src/actions/onStartup.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ const onStartup = async (context: Context): Promise<void> => {
3535

3636
// NEW: no stored tutorial, must start new tutorial
3737
if(!tutorial||!tutorial.id){
38-
if(!!TUTORIAL_URL){
39-
// NEW_FROM_URL
38+
if(TUTORIAL_URL){
39+
// if a tutorial URL is added, launch on startup
4040
try{
4141
consttutorialRes=awaitfetch(TUTORIAL_URL)
4242
consttutorial=awaittutorialRes.json()

src/environment.ts

+3
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,6 @@ export const CONTENT_SECURITY_POLICY_EXEMPTIONS: string | null =
4646

4747
// optional token for authorization/authentication of webhook calls
4848
exportconstWEBHOOK_TOKEN=process.env.CODEROAD_WEBHOOK_TOKEN||null
49+
50+
// a path to write session state to a file. Useful for maintaining session across containers
51+
exportconstSESSION_STORAGE_PATH=process.env.CODEROAD_SESSION_STORAGE_PATH||null

src/services/context/state/Position.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class Position {
1919
setTutorial(workspaceState: vscode.Memento,tutorial: TT.Tutorial): void{
2020
this.storage=newStorage<T.Position>({
2121
key: `coderoad:position:${tutorial.id}:${tutorial.version}`,
22+
filePath: 'coderoad_position',
2223
storage: workspaceState,
2324
defaultValue,
2425
})

src/services/context/state/Tutorial.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Tutorial {
99
constructor(workspaceState: vscode.Memento){
1010
this.storage=newStorage<TT.Tutorial|null>({
1111
key: 'coderoad:currentTutorial',
12+
filePath: 'coderoad_tutorial',
1213
storage: workspaceState,
1314
defaultValue: null,
1415
})

src/services/node/index.ts

+20-4
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,41 @@ import { WORKSPACE_ROOT } from '../../environment'
77
constasyncExec=promisify(cpExec)
88
constasyncRemoveFile=promisify(fs.unlink)
99
constasyncReadFile=promisify(fs.readFile)
10+
constasyncWriteFile=promisify(fs.writeFile)
1011

1112
interfaceExecParams{
1213
command: string
1314
dir?: string
1415
}
1516

17+
// correct paths to be from workspace root rather than extension folder
18+
constgetWorkspacePath=(...paths: string[])=>{
19+
returnjoin(WORKSPACE_ROOT, ...paths)
20+
}
21+
1622
exportconstexec=(params: ExecParams): Promise<{stdout: string;stderr: string}>|never=>{
1723
constcwd=join(WORKSPACE_ROOT,params.dir||'')
1824
returnasyncExec(params.command,{ cwd })
1925
}
2026

2127
exportconstexists=(...paths: string[]): boolean|never=>{
22-
returnfs.existsSync(join(WORKSPACE_ROOT,...paths))
28+
returnfs.existsSync(getWorkspacePath(...paths))
2329
}
2430

2531
exportconstremoveFile=(...paths: string[])=>{
26-
returnasyncRemoveFile(join(WORKSPACE_ROOT, ...paths))
32+
returnasyncRemoveFile(getWorkspacePath(...paths))
33+
}
34+
35+
exportconstreadFile=(...paths: string[]): Promise<string|void>=>{
36+
constfilePath=getWorkspacePath(...paths)
37+
returnasyncReadFile(getWorkspacePath(...paths),'utf8').catch((err)=>{
38+
console.warn(`Failed to read from ${filePath}: ${err.message}`)
39+
})
2740
}
2841

29-
exportconstreadFile=(...paths: string[])=>{
30-
returnasyncReadFile(join(...paths))
42+
exportconstwriteFile=(data: any, ...paths: string[]): Promise<void>=>{
43+
constfilePath=getWorkspacePath(...paths)
44+
returnasyncWriteFile(filePath,data).catch((err)=>{
45+
console.warn(`Failed to write to ${filePath}: ${err.message}`)
46+
})
3147
}

src/services/storage/index.ts

+48-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import*asvscodefrom'vscode'
2+
import{readFile,writeFile}from'../node'
3+
import{SESSION_STORAGE_PATH}from'../../environment'
24

35
// NOTE: localStorage is not available on client
46
// and must be stored in editor
@@ -8,31 +10,75 @@ import * as vscode from 'vscode'
810
// forcing it to be passed in through activation and down to other tools
911
classStorage<T>{
1012
privatekey: string
13+
privatefilePath: string
1114
privatestorage: vscode.Memento
1215
privatedefaultValue: T
13-
constructor({ key, storage, defaultValue }: {key: string;storage: vscode.Memento;defaultValue: T}){
16+
constructor({
17+
key,
18+
filePath,
19+
storage,
20+
defaultValue,
21+
}: {
22+
key: string
23+
filePath: string
24+
storage: vscode.Memento
25+
defaultValue: T
26+
}){
1427
this.storage=storage
1528
this.key=key
29+
this.filePath=filePath
1630
this.defaultValue=defaultValue
1731
}
1832
publicget=async(): Promise<T>=>{
1933
constvalue: string|undefined=awaitthis.storage.get(this.key)
2034
if(value){
2135
returnJSON.parse(value)
36+
}elseif(SESSION_STORAGE_PATH){
37+
try{
38+
// optionally read from file as a fallback to local storage
39+
constsessionFile=awaitreadFile(SESSION_STORAGE_PATH,`${this.filePath}.json`)
40+
if(!sessionFile){
41+
thrownewError('No session file found')
42+
}
43+
constdata: T=JSON.parse(sessionFile)
44+
45+
if(data){
46+
// validate session
47+
constkeys=Object.keys(data)
48+
if(keys.length){
49+
returndata
50+
}
51+
}
52+
}catch(err){
53+
console.warn(`Failed to read or parse session file: ${SESSION_STORAGE_PATH}/${this.filePath}.json`)
54+
}
2255
}
2356
returnthis.defaultValue
2457
}
2558
publicset=(value: T): void=>{
2659
conststringValue=JSON.stringify(value)
2760
this.storage.update(this.key,stringValue)
61+
this.writeToSessionFile(stringValue)
2862
}
2963
publicupdate=async(value: T): Promise<void>=>{
3064
constcurrent=awaitthis.get()
3165
constnext=JSON.stringify({
3266
...current,
3367
...value,
3468
})
35-
this.storage.update(this.key,next)
69+
awaitthis.storage.update(this.key,next)
70+
71+
this.writeToSessionFile(next)
72+
}
73+
publicwriteToSessionFile(data: string){
74+
// optionally write state to file, useful when state cannot be controlled across containers
75+
if(SESSION_STORAGE_PATH){
76+
try{
77+
writeFile(data,SESSION_STORAGE_PATH,`${this.filePath}.json`)
78+
}catch(err: any){
79+
console.warn(`Failed to write coderoad session to path: ${SESSION_STORAGE_PATH}/${this.filePath}.json`)
80+
}
81+
}
3682
}
3783
publicreset=()=>{
3884
this.set(this.defaultValue)

0 commit comments

Comments
 (0)
close