사용자:Hsl0/연구소/숫자야구 live/실시간.js
보이기
참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.
- 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
- 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
- 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
const api = new mw.Api();
const TIMEOUT = Symbol('timeout_id');
const STOPPED = Symbol('is_stopped');
const RUNNING = Symbol('is_running');
const THROTTLE = Symbol('throttle_timeout_id');
export class LiveClient extends EventTarget {
constructor(title, options = {}) {
if(!title) throw new TypeError('문서를 지정해 주세요');
super();
this.src = title;
this.interval = options.interval || {
throttle: null,
required: null
};
this.loadParams = options.loadParams || {
action: "query",
curtimestamp: true,
prop: "revisions",
titles: this.src,
formatversion: 2,
rvprop: ['ids', 'timestamp', 'user', 'content'],
rvlimit: "max",
rvdir: "newer",
};
this.postParams = options.postParams || {
action: "edit",
curtimestamp: true,
title: this.src,
minor: true
};
this.now = options.from;
}
// 1회 불러오기
load(from, options = {}) {
const params = {};
// 시작 리비전
switch(typeof from) {
case 'string':
params.rvcontinue = from;
break;
case 'object':
if(from.timestamp) {
if(from.id) params.rvcontinue = from.timestamp + '|' + from.id;
else params.rvfrom = from.timestamp;
} else if(from.id) params.rvfromid = from.id;
break;
}
return api.get(Object.assign(params, this.loadParams, options.params), options.ajax).then(response => {
if(response.error) {
// API 에러
throw {
type: 'api',
error: response.error,
target: from,
response
};
} else {
const revisions = response.query && response.query.pages[0].revisions;
const result = {
response,
revisions,
target: from
};
// 갱신됨 (후순위 동작)
if(!options.silent) setTimeout(() => super.dispatchEvent(new CustomEvent('update', {
detail: result
})), 0);
return result;
}
}, error => {
// HTTP 에러
throw {
type: 'fetch',
error,
target: from,
response: null
};
});
}
// 데이터 전송
post(content) {
return api.postWithEditToken(Object.assign({
text: content
}, this.postParams));
}
// 계속 불러오기 (간격 재설정)
start(interval) {
const client = this;
Object.assign(this.interval, interval); // 시간 간격 갱신
if(typeof this.interval !== 'object' && (typeof this.interval.throttle !== 'number' || typeof this.interval.required !== 'number')) {
throw new TypeError('정상적인 간격이 지정되지 않았습니다');
}
function load(options) {
// 다음 폴링
function next() {
if(throttle && request) client[TIMEOUT] = setTimeout(load, client.interval.required, {
params: {
maxage: 0
}
});
}
let throttle = false; // 최소 시간 경과 여부
let request = false; // 요청 완료 여부
client[TIMEOUT] = null;
client[RUNNING] = true;
// 일정 시간이 지나기 전에는 다음 요청을 하지 않음
client[THROTTLE] = setTimeout(() => {
throttle = true;
client[THROTTLE] = null;
next();
}, client.timeout.throttle);
// AJAX 요청
client.load(client.now, options).then(result => {
if(client[STOPPED]) client[STOPPED] = false; // 정지가 예약되면 무시하고 다음 예약 해제
else {
const response = result.response;
const revisions = result.revisions;
result.error = false;
result.updated = Boolean(revisions);
delete result.revisions;
// 로드 완료
super.dispatchEvent(new CustomEvent('fetch', {
detail: result
}));
if(revisions) {
// 이번 요청에서 데이터가 갱신되었을 때
if(response.continue) {
// 더 불러올 데이터가 있으면 바로 요청
client.now = response.continue.rvcontinue;
return load();
} else {
// 더 불러올 데이터가 없으면
const last = revisions[revisions.length - 1];
client.now = {
id: last.revid,
timestamp: last.timestamp
};
client.now.id++;
}
}
request = true; // 데이터 불러옴
next(); // 다음 요청 시도
}
}, error => {
if(client[STOPPED]) client[STOPPED] = false; // 정지가 예약되면 무시하고 다음 정지 예약 해제
else {
// 에러 이벤트
super.dispatchEvent(new CustomEvent('error', {
detail: error
}));
// 서버에 요청이 정상적으로 보내졌으나 API 에러가 발생한 경우 (넷코드용)
if(error.type === 'api') super.dispatchEvent(new CustomEvent('fetch', {
detail: {
response: error.response,
error: true,
updated: false
}
}));
this[RUNNING] = false; // 에러가 발생하면 정지
}
});
// 요청 시도 이벤트
super.dispatchEvent(new CustomEvent('request', {
detail: {
target: client.now,
params: options && options.params,
ajax: options && options.ajax
}
}));
}
if(!this[RUNNING]) load(); // 자동 요청이 실행중이 아니라면 시작
return this; // 메소드 체이닝
}
stop(noStrict) {
if(this[RUNNING]) { // 자동 요청이 실행중이면
// 필수 대기시간
if(this[TIMEOUT]) {
clearTimeout(this[TIMEOUT]);
this[TIMEOUT] = null;
} else this[STOPPED] = true; // 요청중이면 결과 무시 예약
// 쓰로틀 대기중이면
if(this[THROTTLE]) {
clearTimeout(this[THROTTLE]);
this[THROTTLE] = null;
}
this[RUNNING] = false; // 정지로 표시
} else if(!noStrict) throw new Error('자동 로드가 실행중이 아닙니다');
return this; // 메소드 체이닝
}
// 현재(다음) 리비전 (지정된 리비전으로) 초기화
reset(from) {
if(this[RUNNING]) stop(); // 일단 진행중인 요청 정지
this.now = from; // 리비전 위치 변경/초기화
start(); // 다시 시작
return this; // 메소드 체이닝
}
dispatchEvent() {
throw new TypeError('이벤트를 임의로 발생시킬 수 없습니다');
}
// 자동 요청 실행 여부
get running() {
return this[RUNNING];
}
}