Theme
The event context Preview
When an event fires, the Connector takes a snapshot of what happened and hands it to your script. The snapshot is frozen for the whole execution. It cannot change under you while the script runs, no matter what the user does next, which is exactly the property you want when you are about to make a decision about a file.
The context functions
| Call | Returns |
|---|---|
CtxRelPath() | The path the operation is about, relative to the virtual file system root |
CtxRelTargetPath() | The destination path on a rename or move; empty otherwise |
CtxUsername() | The user the operation was performed for |
CtxVFSName() | The name of the virtual file system it happened in |
CtxTargetVFSName() | On before.transfer and after.transfer, the virtual file system the destination lands in; empty otherwise |
EventHandler() | The event id, for example before.upload |
HandlerPriority() | The priority of the handler that invoked this script |
EventCtx() | All of the above as one object |
All of them return an empty string rather than null when the event has no such value, so they are always safe to call and never need a guard. Which events populate which values is in Events and when they fire.
js
Log.Info(CtxUsername() + " is about to upload " + CtxRelPath() +
" into " + CtxVFSName() + " (" + EventHandler() + ")");Paths are always POSIX and always relative to the virtual file system root, whatever the Connector is running on and whatever the storage underneath is. A path is /reports/q1.csv, never C:\Data\reports\q1.csv.
EventCtx()
EventCtx() returns the whole snapshot as a single object. It is the fastest way to see what an event actually gave you:
js
Log.Info(JSON.stringify(EventCtx()));json
{
"Event": "before.rename",
"VfsName": "MainStorage",
"Src": "/docs/draft.pdf",
"Dst": "/docs/final.pdf",
"Sess": "2fJxK9pQ7wRt",
"User": "jsmith",
"Priority": 10,
"Vfs": null
}These field names are not the Syncplify Server field names
On Syncplify Server, EventCtx() returns RelPath, RelTargetPath, VFSName, Username and VirtualSite. On the Connector it returns Event, VfsName, Src, Dst, Sess, User and Priority. A script that reads EventCtx().RelPath here gets undefined, silently.
Prefer the Ctx* functions. They are named the same in both products and behave the same, so code written against them is portable and code written against the object is not. Use EventCtx() for logging and diagnostics, where seeing everything at once is the point.
The Vfs field is an internal handle and always serializes as null. Use GetCurrentVFS() to get a usable virtual file system object.
| Field | Same as |
|---|---|
Event | EventHandler() |
VfsName | CtxVFSName() |
Src | CtxRelPath() |
Dst | CtxRelTargetPath() |
DstVfsName | CtxTargetVFSName() |
User | CtxUsername() |
Priority | HandlerPriority() |
Sess | Session.GetID() |
Vfs | Not readable from script; call GetCurrentVFS() |
The Session object
Session carries the same facts in the shape Syncplify Server scripts use, so code that reads them stays portable between the two products. Unlike Syncplify Server, Session on the Connector is never null; there is no event that fires before it exists.
| Method | Returns |
|---|---|
Session.GetRelPath() | Same as CtxRelPath() |
Session.GetAbsPath() | Same as CtxRelPath() |
Session.GetRelTargetPath() | Same as CtxRelTargetPath() |
Session.GetAbsTargetPath() | Same as CtxRelTargetPath() |
Session.GetUserID() | Same as CtxUsername() |
Session.GetLoggedInUserID() | Same as CtxUsername() |
Session.GetID() | The session key of the tunnel session the operation arrived on |
Session.GetCurrentVFSName() | Same as CtxVFSName() |
Session.CurrentVFSID() | Same as CtxVFSName() |
Session.GetTargetVFSName() | Same as CtxTargetVFSName() |
Session.GetProtocol() | Always the string R2FS |
Session.RemoveFromDirList(pattern) | Hides entries from a listing; only meaningful in a dirList.filter handler |
Session.RemoveFromListDir(pattern) | Deprecated spelling of the same thing, kept so old scripts still run |
Absolute and relative are the same thing here
GetAbsPath() returns the VFS relative path, identical to GetRelPath(). It is not the path on disk. The Connector deliberately does not expose where a virtual file system physically lives to script code, because a virtual file system may be an S3 bucket or an Azure container where the question has no answer. If you need the real bytes, go through the virtual file system object, which handles every backend and decrypts at rest storage for you.
Session.GetProtocol() returns R2FS and never anything else. The Connector genuinely does not know whether the person at the far end used SFTP, FTPS or the web client; that is the Head's business. Branching on the protocol is not possible in a Connector script.
Methods that exist but refuse
These are present so that a ported script fails loudly at the exact line rather than quietly doing nothing. Each throws a descriptive error you can catch:
| Method | Why it cannot work here |
|---|---|
Session.GetRemoteAddress() | The Connector sees the tunnel, not the user's client. The remote address is known to the Head |
Session.GetVirtualSite() | The Connector has no virtual sites |
Session.GetSrvConfig() | There is no Syncplify Server configuration to read |
Session.Terminate() | Sessions are the Head's to end. Refuse the operation with Exit(1) instead |
Session.BlockOperation() | Same effect is achieved with Exit(1) on a before handler |
Session.AddQuestion() | Authentication does not happen on the Connector |
js
try {
Log.Info(Session.GetRemoteAddress());
} catch (e) {
Log.Warn("no remote address on a Connector: " + e);
}Everything else from the Syncplify Server Session class, including GetUser(), GetClientVersion(), GetStartTime(), GetLastCommand() and the custom data methods, is not defined at all. Calling one is a TypeError that stops the script. The list is on If you know Syncplify Server.
Knowing which script you are
| Call | Returns |
|---|---|
ScriptID() | The script's id, the same one that appears in the Connector log |
ScriptName() | The script's name as you typed it in the console |
ExecutionTimeout() | The handler's timeout, in whole seconds |
ExecutionTimeLeft() | Time remaining before the deadline |
ExecutionTimeLeft() is a duration, not a plain number. It converts to nanoseconds in arithmetic, and also exposes .Seconds():
js
var secondsLeft = ExecutionTimeLeft() / 1e9; // works
var alsoSeconds = ExecutionTimeLeft().Seconds(); // also works
Log.Info("seconds left: " + secondsLeft);Putting the script name into your own log lines makes a busy Connector log far easier to read when several handlers are bound to the same event:
js
Log.Info("[" + ScriptName() + "] " + CtxRelPath());Stopping early
js
Exit(); // clean stop, nothing logged
Exit(0); // identical to Exit()
Exit(1); // error stop: logged, and on a before handler it REFUSES the operationExit() is the idiomatic way to stop. Do not use return at the top level of a script; the source is not a function body and return there is a syntax error.
You never need Exit() at the end of a script. Reaching the last line is a clean exit already.
| How a script ends | Handler result | Effect on a fail closed before handler |
|---|---|---|
| Runs off the end | success | Operation proceeds |
Exit() or Exit(0) | success | Operation proceeds |
Exit(n) where n is not 0 | failure | Operation refused |
Uncaught throw | failure | Operation refused |
| Timeout | failure | Operation refused |
On an after handler none of those refuse anything; the operation has already committed, so a failure is written to the log and the user never learns of it.
The exit code itself carries no meaning beyond zero or not zero. Exit(1) and Exit(42) refuse identically; the number appears in the log, which makes it a cheap way to tell two refusal reasons apart when you are reading logs later.