TouchDevelop/rt/ticker.ts

930 строки
25 KiB
TypeScript

///<reference path='refs.ts'/>
module TDev {
// device signature:
// touch-enabled
// physical keyboard present
// user-agent
// portrait vs landscape
export enum Ticks {
noEvent,
mainInit,
mainHashChange,
mainKeyEvent,
mainShortcutKeyEvent, // i.e., not coming from textbox/textarea
mainResetWorld,
artSoundPreviewPlay,
docsEdit,
exportAzure,
exportCordova,
exportHTML5,
scriptPropsIconArt,
scriptPropsSplashArt,
hourOfCodeDoneStep,
hourOfCodeKeepCoding,
hourOfCodeConfirm,
hourOfCodeDone,
hourOfCodeMore,
hourOfCodeFinal,
tutorialTranslateSplash,
tutorialTranslateStep,
tutorialTranslateScript,
tutorialKeepEditing,
tutorialMoreTutorials,
tutorialNextStep,
dodStart,
dodDrop,
dodWrongTarget,
cordovaBuild,
translateDocTopic,
legalNoticeSignIn,
legalNoticeAgree,
calcAddMissingArgs,
calcBackspace,
calcCopy,
calcCut,
calcDedicatedOp,
calcEdit,
calcEditString,
calcExtract,
calcPromoteToParameter,
calcGoToDef,
calcFindRefs,
calcUnselect,
calcExtend,
calcReplaceInScript,
calcReplaceInSelection,
calcReplaceInAction,
calcInsertAssignment,
calcInsertString,
calcIntelliButton,
calcIntelliButton0,
calcIntelliButton1,
calcIntelliButton2,
calcIntelliButton3,
calcIntelliButton4,
calcIntelliButton5plus,
calcIntelliLiteral,
calcIntelliLocal,
calcIntelliProperty,
calcIntelliPropertyPrimary,
calcIntelliSingleton,
calcIntelliLibrary,
calcIntelliResource,
calcKeyboardSearch,
calcMoveCursorLeft,
calcMoveCursorRight,
calcNewLine,
calcNextIntelliTopPage,
calcNextIntelliPageSearch,
calcNextIntelliPage,
calcNextIntelliPage0,
calcNextIntelliPage1,
calcNextIntelliPage2,
calcNextIntelliPage3,
calcNextIntelliPage4,
calcNextIntelliPage5plus,
calcAsync,
calcAwait,
calcNot,
calcNumber,
calcPaste,
calcPickColor,
calcPickNumber,
calcDemoteIntoLocal,
calcPromoteIntoGlobal,
calcPromoteIntoField,
calcRenameLocal,
calcRenameProperty,
calcSearchBack,
calcSearchRun,
calcSimplify,
calcStrip,
calcSpecialKey,
calcStartSearch,
calcStoreInVar,
calcSwapBoolean,
calcSwapAsync,
calcIntelliAsync,
calcSwitchToNormal,
calcSwitchToNumber,
calcTrueFalse,
calcUndo,
calcGoToBoxProperty,
calcAddBoxProperty,
calcInsertStringParamterValue,
calcHelp,
calcElseIf,
calcUnElseIf,
calcBindGlobal,
calcFixItAtomic,
calcEditArt,
calcAddOptionalParameter,
calcChangeOptionalParameter,
codeAddAbove,
codeAddBelow,
codeShow,
codeContinue,
codeReturn,
codeBreak,
codeBoxed,
codeCopy,
codeCopySelection,
codeDeleteSelection,
codeCut,
codeCutSelection,
codeCycleSidePane,
codeCycleSidePaneFull,
codeExtractAction,
codeFocusSidePane,
codeFocusSidePaneFull,
codeFor,
codeForEach,
codeHub,
codeIf,
codeMoveLeft,
codeNewVar,
codePaste,
codeRun,
codeResume,
codeStartSelection,
codeSurround,
codeUndo,
codeSplit,
codeWhile,
codeUninstallScript,
codeBackFromSearch,
actionPropPrivate,
actionPropTest,
actionPropAtomic,
btnPaste,
btnCopy,
btnCut,
btnSelect,
btnTryFix,
btnUndoFix,
btnAddUp,
btnAddDown,
btnMoveLeft,
btnBreakpoint,
btnChangeKind,
calcBtnBackspace,
calcBtnUndo,
calcBtnNumberKeypad,
calcBtnMiscKeypad,
calcBtnNormalKeypad,
calcBtnApiSearch,
editBtnSideSearch,
chooseCancel,
editTapBelow,
coverageShown,
coverageOpenInEditor,
coverageBucketSurveyActionEdit,
coverageBucketSurveyStatementEdit,
coverageBucketSurveyDebugger,
coverageBucketSurveyBreakpoint,
coverageBucketSurveyExceededSuccessfully,
profileShown,
profileOpenInEditor,
debuggerExit,
debuggerContinue,
debuggerStepIn,
debuggerStepOver,
debuggerStepOut,
debuggerToggleBreakpoint,
debuggerViewInit,
debuggerPauseWall,
debuggerShowValues,
debuggerShowStack,
debuggerGotoWall,
debuggerGotoCurrent,
debuggerAppLog,
debuggerHelp,
debuggerValueClicked,
debuggerKeyboardEvent,
editorTutorialClose,
editorTutorialNext,
editorTutorialPrevious,
sideAddAction,
sideAddActionTest,
sideAddActionTypeDef,
sideAddEvent,
sideAddLibrary,
sideAddPage,
sideAddRecord,
sideAddObject,
sideAddTable,
sideAddIndex,
sideAddDecorator,
sideAddResource,
sideAddVariable,
sideAddAnything,
sideHelp,
sideBraces,
sideDebug,
sideLogs,
sideErrors,
sideDeployWebSite,
sidePlugins,
sideButtonPlugin,
sidePreview,
sideHistory,
sideHistoryGoTo,
sidePaste,
sideScript,
sideScriptGoToDecl,
sideSearch,
sideSearchGoToDecl,
sideSearchGoToStmt,
sidePublish,
sideShare,
sideDisconnect,
sideRun,
sideCut,
sideCopy,
sideDelete,
sideFindRefs,
sideActionHeaderInit,
sideCommentInit,
sideParameterInit,
sideResetSidePane,
sideInlineActionInit,
sideExport,
sideAllTests,
sideTestOne,
sideEditLibrary,
sideEditString,
sideUpdate,
sideUpdateOne,
sideActionAddInput,
sideActionAddOutput,
sideMoveToLibrary,
sideTutorialCancel,
sideTutorialRedisplay,
sideMoreOptions,
appsCreateAzureWebsite,
appsDeployWebsite,
pluginStop,
pluginRunOperation,
pluginAddMore,
pluginRunAnnotationOperation,
searchApiSynthesis,
searchApiSearchArt,
searchApiSearchLib,
searchApiSearchAuto,
searchApiInsertArt,
searchApiUploadArt,
searchApiInsertLib,
wallBack, // do not rename, or fix tipmanager
wallJS,
wallEdit,
wallReplay,
wallScreenshot,
wallLogs,
wallStop,
wallStopForce,
wallPause,
wallResume,
wallRun,
wallTraces,
wallAddHeart,
wallRemoveHeart,
commentAttach,
commentBugTracking,
coreRun,
coreResume,
coreRerun,
corePublishHidden,
corePublishPublic,
runtimePlayAgain,
runtimeBack,
appUpdateAvailable,
appNoUpdate,
appQuickUpdate,
appUpdate,
editorUpdateScript,
editorUpdateLibrary,
editorRunWithProfiling,
editorRunWithCoverage,
editorRunAsMinion,
viewActionInit,
viewLibraryRefInit,
viewRecordInit,
viewScriptInit,
viewVariableInit,
hubCreateScript,
hubCreateGame,
hubCreateGroup,
hubCreateList,
hubJoinGroup,
hubUploadPicture,
hubUploadSound,
hubGameTutorial,
hubWinStore,
hubDocs,
hubFirstTutorial,
hubDocsTutorial,
hubBasicEditorVideo,
hubBasicEditorVideoVoice,
hubBasicEditorVideoSubtitles,
hubBeginnersGettingStarted,
hubChatSearch,
hubRateTouchdevelop,
hubChooseSkill,
hubDevBootCamp,
hubDocsApi,
hubDocsWhatsNew,
hubAdvancedEditorVideo,
hubSeeMoreMyScripts,
hubSeeMoreNewScripts,
hubSeeMoreArt,
hubSeeMoreUsers,
hubSeeMoreCloudOther,
hubSeeMoreShowcase,
hubSeeMoreTopScripts,
hubSeeMoreDocs,
hubSeeMoreCloud,
hubSeeMoreGroups,
hubSeeMoreLists,
hubMyScriptsShowcase,
hubMyScriptsTemplate,
hubNotifications,
hubUpdates,
hubDoUpdates,
hubTests,
hubShowcaseMgmt,
hubBenchmarks,
hubForum,
hubFacebook,
hubTwitter,
hubYouTube,
hubSettings,
hubAbout,
hubHelp,
hubWrongTime,
hubTag,
hubTagFromList,
hubTagSearch,
hubFeedback,
hubChooseWallpaper,
changeSkillScriptProperties,
changeSkillActionProperties,
changeSkillScriptExplorer,
editorSkillBlock,
editorSkillClassic,
editorSkillCurly,
groupCodeReset,
groupCodeNew,
groupCodeGet,
groupDelete,
groupLeave,
groupJoin,
groupAllowAnyoneToJoin,
groupRequireInvitationCodeToJoin,
groupChangePicture,
publishShareGroup,
browseRun,
browseEdit,
browsePin,
browseHeart,
browseUnHeart,
browsePublish,
browseUninstall,
browsePush,
browseDiffBase,
browseConvertToTutorial,
browseConvertToLesson,
browseAddScriptToList,
browseUpdate,
browseSendPullRequest,
browsePublicationNotes,
browseListBugs,
browseListReleases,
browseShare,
browseListMyScripts,
browseListNew,
browseListTop,
browseListShowcase,
browseListForum,
browseListDocs,
browseListHelp,
browseListGroups,
browseListArt,
browseListMyArt,
browseListTags,
browseListSearch,
browseListUsers,
browseListLists,
browseFollowTopic,
introSlideGetStarted,
introSlideLimitations,
introSlideCreateApps,
introSlidePopCorn,
introSlideLogIn,
introSlideDismiss,
recordAddKey,
recordAddValue,
recordAddAction,
recordPersTemporary,
recordPersLocal,
recordPersCloud,
recordPersPartial,
recordExported,
dbgEvent,
dbgLogEvent,
learnPlayVideo,
learnBrowseDoc,
offlineLoginSync,
offlineLoginSyncNoToken,
offlineLoginGettingStarted,
offlineEnsureLogin,
offlineEnsureLoginOk,
translateNagDisplay,
translateNagOk,
benchmarksNagDisplay,
benchmarksNagDismiss,
benchmarksNagRunOne,
benchmarksNagRunSuite,
songAlbumPlay,
songPlay,
warningNotificationTap,
crashDialogEdit,
crashDialogDebug,
toLibNewLib,
toLibExistingLib,
toLibOKtoMove,
toLibAdvanceSelectedToPending,
toLibMakeTheMove,
toLibDiscardPending,
toLibExitToEditor,
collabFirstLoad,
collabResume,
collabRecordAst,
collabRealMerge,
collabPostChatMessage,
collabStartCollaboration,
collabStopCollaboration,
codeCompile
}
export interface TickEvent
{
timestamp:number;
event:Ticks;
arg?:string;
}
export interface BugReport {
exceptionConstructor: string;
exceptionMessage: string;
context: string;
currentUrl: string;
jsUrl: string;
scriptId: string;
stackTrace: string;
sourceURL: string;
line: number;
eventTrace: string;
userAgent: string;
resolution: string;
timestamp: number;
platform: string[];
worldId: string;
kind: string;
attachments: string[];
tdVersion?: string;
}
export interface TicksReport {
dateStr: string;
sessionEvents: any;
platform: string[];
worldId: string;
jsUrl: string;
}
export interface RecordedEvents
{
dateStr:string; // YYYY.MM.DD
sessionEvents:any; // { (tickName: number)* }
chunkId:number;
}
export module Ticker {
var logMsgs:TickEvent[] = [];
var logSz = 200;
var logIdx = -1;
var thresholdTime = -1
var dateStr = ""
var sessionEvents:any = {}
var disabled = false;
var delay = 10; // initial, 10s
var chunkId = 1;
var maxDelay = 1200; // 1200s
var initialized = false;
export var mainJsName = "unknown";
export var fillEditorInfoBugReport: (b: BugReport) => void;
export var fillEditorInfoTicksReport: (b: TicksReport) => void;
function setDate():void
{
var now = new Date()
dateStr = Util.fmt("{0}.{1}.{2}", now.getFullYear(), now.getMonth() + 1, now.getDate())
if (delay > maxDelay)
delay = maxDelay;
thresholdTime = now.getTime() + delay*1000;
}
export function disable()
{
disabled = true
}
export function init()
{
var d = window.localStorage["ticksDelay"] * 1;
if (d) delay = d;
setDate();
var archived = window.localStorage["archivedEvents"]
if (archived) {
var prevEvents:RecordedEvents[];
try {
prevEvents = JSON.parse(archived)
} catch (e) {
Util.log("bogus JSON in archivedEvents: " + archived)
Util.check(false, "malformed JSON in archived events");
delete window.localStorage["archivedEvents"]
}
if (prevEvents) {
prevEvents.forEach((e) => {
chunkId = Math.max(e.chunkId, chunkId)
});
chunkId++;
var last = prevEvents.peek()
if (!!last && last.dateStr == dateStr) {
sessionEvents = last.sessionEvents
prevEvents.pop()
window.localStorage["archivedEvents"] = JSON.stringify(prevEvents)
}
sendOutEvents(prevEvents);
}
}
initialized = true;
}
export function saveCurrent(sendCurrent = false)
{
var prevEvents:RecordedEvents[] = []
var archived = window.localStorage["archivedEvents"]
if (archived)
prevEvents = JSON.parse(archived)
prevEvents.push({
sessionEvents: sessionEvents,
dateStr: dateStr,
chunkId: chunkId++
})
prevEvents = prevEvents.filter((e) => !!e.dateStr)
if (prevEvents.length > 30)
prevEvents = prevEvents.slice(prevEvents.length - 30)
var newVal = JSON.stringify(prevEvents)
if (newVal.length > 50000)
newVal = "[]" // drop it if it's getting big
window.localStorage["archivedEvents"] = newVal
if (sendCurrent && Cloud.getAccessToken() && Cloud.isOnline())
(<any>sendOutEvents)(prevEvents); // TSBUG: stack overflow
}
function checkDate():void
{
if (Util.now() > thresholdTime) {
setDate()
saveCurrent(true)
RT.Perf.saveCurrent(true);
if (delay < maxDelay) {
delay *= 1.1;
window.localStorage["ticksDelay"] = delay + "";
}
sessionEvents = {}
}
}
function sendOutEvents(events:RecordedEvents[])
{
if (events.length == 0) return;
var currId = events[0].chunkId
var req = <TicksReport>{
dateStr: events[0].dateStr,
sessionEvents: events[0].sessionEvents,
platform: Browser.platformCaps,
worldId: "",
jsUrl: mainJsName
}
if (fillEditorInfoTicksReport)
fillEditorInfoTicksReport(req);
Cloud.postTicksAsync(req).done(() => {
var events = window.localStorage["archivedEvents"]
if (events) {
var newEvents:RecordedEvents[] = JSON.parse(events);
window.localStorage["archivedEvents"] = JSON.stringify(newEvents.filter((t) => t.chunkId != currId))
sendOutEvents(newEvents)
}
}, e => { }) // ignore error; will try again later
}
export function tickName(t:Ticks):string
{
return enumToString(Ticks, t);
}
export function tickN(t:Ticks, v:number)
{
tick(t + Util.boundTo(0, v, 5))
}
export function dbg(f:string, ...args:any[])
{
var msg = Util.fmt_va(f, args);
tick(Ticks.dbgEvent, msg);
}
export function localStorageState()
{
if (!window || !window.localStorage) return "";
var l = window.localStorage;
var r = "";
for (var i = 0; i < l.length; ++i) {
r += l.key(i) + " -> " + l[l.key(i)].length + ", ";
}
return r;
}
export function mkBugReport(err:any, ctx = "")
{
var r:BugReport = {
exceptionConstructor: "(unknown)",
exceptionMessage: "(unknown)",
context: ctx,
currentUrl: "",
worldId: "",
kind: "",
scriptId: "",
stackTrace: "",
sourceURL: "",
line: -1,
eventTrace: "",
userAgent: "",
resolution: "",
jsUrl: mainJsName,
timestamp: Util.now(),
platform: [],
attachments: [],
tdVersion: Cloud.config.tdVersion || "",
}
if (fillEditorInfoBugReport)
fillEditorInfoBugReport(r);
if (Array.isArray(err.bugAttachments))
r.attachments.pushRange(err.bugAttachments)
try {
var isDatabaseError = Util.isError(err, e => e.isDatabaseError);
var isNetworkError = Util.isError(err, e => e.isNetworkError);
r.kind = isDatabaseError ? "databaseError" : isNetworkError ? "networkError" : "";
if (!err) r.exceptionMessage = "(null)";
else if (err.message) {
r.exceptionMessage = err.message + "";
if (err.stack)
r.stackTrace = err.stack + "";
else if (err.sourceURL) // Safari doesn't give full stack trace, just top-level method
r.stackTrace = "@" + err.sourceURL + ":" + err.line;
} else if (Array.isArray(err)) {
r.exceptionMessage = err.join("\n");
} else {
r.exceptionMessage = err + "";
}
if (err && err.name && err.name != "Error")
r.exceptionConstructor = err.name;
else
r.exceptionConstructor = r.exceptionMessage.substr(0, 40);
if (isDatabaseError)
r.exceptionConstructor = "DB " + r.exceptionConstructor;
if (/autotest-/.test(ctx))
r.exceptionConstructor = "TEST " + r.exceptionConstructor;
if (err.wabCrashInfo) {
r.stackTrace = err.wabCrashInfo + "\nJavaScript:\n" + r.stackTrace;
r.exceptionConstructor = "WAB " + r.exceptionConstructor;
}
if (r.exceptionMessage && /\[object /.test(r.exceptionMessage)) {
var msg = r.exceptionMessage;
var errorInfo = Util.getErrorInfo(err);
if (errorInfo)
msg += " " + errorInfo;
else
Object.keys(err).forEach((k) => {
if (msg.length < 2000) {
msg += " " + k + ": '" + err[k] + "'";
}
});
r.exceptionMessage = msg;
}
if (/QUOTA/.test(r.exceptionMessage))
r.exceptionMessage += " " + localStorageState();
Ticker.dbg("CRASH REPORT " + r.exceptionMessage); // in case there is another crash report later
if (err.sourceURL)
r.sourceURL = err.sourceURL;
if (err.line)
r.line = err.line;
} catch (e) {
debugger;
}
try {
r.eventTrace = getRecentEvents().map((e) => {
var s = 1000000000 + (r.timestamp - e.timestamp) + "";
s = s.slice(-9);
return s.slice(0, 6) + "." + s.slice(6, 9) + ": " + tickName(e.event) + (e.arg ? "|" + e.arg : "");
}).join("\n")
if (!r.eventTrace)
r.eventTrace = Util.getLogMsgs().map(m => m.elapsed + ": " + m.msg).join("\n");
} catch (e) {
debugger;
}
if ((<any>window).tdAppInsights)
try {
(<any>window).tdAppInsights.trackException(err, r);
} catch (e) { }
return r;
}
export function bugReportToString(b:BugReport)
{
var e = (s:string) => s.replace(/\r?\n/, " ");
return "Error: " + e(b.exceptionMessage) + "\n" +
"URL: " + e(b.currentUrl) + " (script: " + e(b.scriptId) + ", context: " + e(b.context) + ")\n" +
"JS-URL: " + e(b.jsUrl) + "\n" +
"Platform: " + e(Browser.platformCaps.join(", ")) + "\n" +
"UserAgent: " + e(b.userAgent) + " (resolution: " + e(b.resolution) + ")\n" +
"Date: " + new Date(b.timestamp).toString() + "\n" +
"\n" +
"RecentEvents:\n" + b.eventTrace + "\n\n" +
"StackTrace:\n" + b.stackTrace + "\n\n";
}
export function bugReportForHash(b:BugReport)
{
return b.exceptionMessage + " " + b.stackTrace;
}
export function rawTick(tn:string)
{
if (!initialized || disabled) return;
checkDate();
tn = tn.replace(/[^a-zA-Z_]/g, "_")
Util.log("TICK: " + tn)
if (sessionEvents[tn])
sessionEvents[tn]++;
else
sessionEvents[tn] = 1;
}
function tickBase(t: Ticks, sep: string, arg?: string) {
if (t == Ticks.noEvent || !t) return;
var tn = tickName(t)
if (arg)
tn += sep + arg;
if (t == Ticks.dbgLogEvent) {
t = Ticks.dbgEvent;
} else if (t == Ticks.dbgEvent)
Util.log("DBG: " + tn);
else Util.log("TICK: " + tn);
if (!initialized || disabled) return;
checkDate();
var m = <TickEvent>{ timestamp: Util.now(), event: t, arg: arg }
if (logIdx >= 0) {
logMsgs[logIdx++] = m;
if (logIdx >= logSz) logIdx = 0;
} else {
logMsgs.push(m);
if (logMsgs.length >= logSz)
logIdx = 0;
}
// this one we only wanted logged, not counted
if (t != Ticks.dbgEvent) {
if (sessionEvents[tn])
sessionEvents[tn]++;
else
sessionEvents[tn] = 1;
if ((<any>window).tdAppInsights)
try {
(<any>window).tdAppInsights.trackEvent(tn);
} catch (e) { }
}
}
export function tickArg(t: Ticks, arg?: string) {
return tickBase(t, "$", arg);
}
export function tick(t:Ticks, arg?:string)
{
return tickBase(t, "|", arg);
}
export function getRecentEvents()
{
if (logIdx >= 0)
return logMsgs.slice(logIdx).concat(logMsgs.slice(0, logIdx));
else
return logMsgs.slice(0);
}
}
export function tick(t: Ticks, arg?: string) { Ticker.tick(t, arg) }
export function tickArg(t: Ticks, arg?: string) { Ticker.tickArg(t, arg) }
export function tickN(t:Ticks, v:number) { Ticker.tickN(t, v) }
}