react-kakao-maps-sdk
Version:
React components for using kakao map api
217 lines (214 loc) • 6.11 kB
JavaScript
import { SIGNATURE } from './constants.js';
let LoaderStatus = /*#__PURE__*/function (LoaderStatus) {
LoaderStatus[LoaderStatus["INITIALIZED"] = 0] = "INITIALIZED";
LoaderStatus[LoaderStatus["LOADING"] = 1] = "LOADING";
LoaderStatus[LoaderStatus["SUCCESS"] = 2] = "SUCCESS";
LoaderStatus[LoaderStatus["FAILURE"] = 3] = "FAILURE";
return LoaderStatus;
}({});
const DEFAULT_ID = `${SIGNATURE}_Loader`;
/**
* Kakao Map Api Loader
*
* `new Loader(options).load()` 함수를 이용하여 Api를 비동기적으로 삽입할 수 있습니다.
*
* 해당 Loader를 이용시 `react-kakao-maps-sdk` 내부에서 injection 되는 이벤트를 감지하여 kakao map api 로딩 이후에 렌더링을 진행합니다.
*/
class Loader {
static loadEventCallback = new Set();
callbacks = [];
done = false;
loading = false;
errors = [];
constructor(_ref) {
let {
appkey,
id = DEFAULT_ID,
libraries = [],
nonce,
retries = 3,
url = "//dapi.kakao.com/v2/maps/sdk.js"
} = _ref;
this.id = id;
this.appkey = appkey;
this.libraries = libraries;
this.nonce = nonce;
this.retries = retries;
this.url = url;
if (Loader.instance && !Loader.equalOptions(this.options, Loader.instance.options)) {
if (!Loader.equalOptions(this.options, Loader.instance.options)) {
switch (Loader.instance.status) {
case LoaderStatus.FAILURE:
throw new Error(`Loader must not be called again with different options. \n${JSON.stringify(this.options, null, 2)}\n!==\n${JSON.stringify(Loader.instance.options, null, 2)}`);
default:
Loader.instance.reset();
Loader.instance = this;
break;
}
}
}
if (!Loader.instance) {
Loader.instance = this;
}
return Loader.instance;
}
get options() {
return {
appkey: this.appkey,
id: this.id,
libraries: this.libraries,
nonce: this.nonce,
retries: this.retries,
url: this.url
};
}
static addLoadEventLisnter(callback) {
if (window.kakao && window.kakao.maps) {
window.kakao.maps.load(callback);
}
Loader.loadEventCallback.add(callback);
return callback;
}
static removeLoadEventLisnter(callback) {
return Loader.loadEventCallback.delete(callback);
}
load() {
return new Promise((resolve, reject) => {
this.loadCallback(err => {
if (!err) {
resolve(window.kakao);
} else {
reject(err);
}
});
});
}
get status() {
if (this.onEvent) {
return LoaderStatus.FAILURE;
}
if (this.done) {
return LoaderStatus.SUCCESS;
}
if (this.loading) {
return LoaderStatus.LOADING;
}
return LoaderStatus.INITIALIZED;
}
get failed() {
return this.done && !this.loading && this.errors.length >= this.retries + 1;
}
loadCallback(fn) {
this.callbacks.push(fn);
this.execute();
}
resetIfRetryingFailed() {
if (this.failed) {
this.reset();
}
}
reset() {
this.deleteScript();
this.done = true;
this.loading = false;
this.errors = [];
this.onEvent = undefined;
}
execute() {
this.resetIfRetryingFailed();
if (this.done) {
this.callback();
} else {
if (window.kakao && window.kakao.maps) {
console.warn("Kakao Maps이 이미 외부 요소에 의해 로딩되어 있습니다." + "설정한 옵션과 일치 하지 않을 수 있으며, 이에 따른 예상치 동작이 발생할 수 있습니다.");
window.kakao.maps.load(this.callback);
return;
}
if (!this.loading) {
this.loading = true;
this.setScript();
}
}
}
setScript() {
if (document.getElementById(this.id)) {
this.callback();
}
const url = this.createUrl();
const script = document.createElement("script");
script.id = this.id;
script.type = "text/javascript";
script.src = url;
script.onerror = this.loadErrorCallback.bind(this);
script.onload = this.callback.bind(this);
script.defer = true;
script.async = true;
if (this.nonce) {
script.nonce = this.nonce;
}
document.head.appendChild(script);
}
loadErrorCallback(event) {
this.errors.push(event);
if (this.errors.length <= this.retries) {
const delay = this.errors.length * 2 ** this.errors.length;
console.log(`Failed to load Kakao Maps script, retrying in ${delay} ms.`);
setTimeout(() => {
this.deleteScript();
this.setScript();
}, delay);
} else {
this.done = true;
this.loading = false;
this.onEvent = this.errors[this.errors.length - 1];
this.callbacks.forEach(cb => {
cb(this.onEvent);
});
this.callbacks = [];
Loader.loadEventCallback.forEach(cb => {
cb(this.onEvent);
});
}
}
createUrl() {
let url = this.url;
url += `?appkey=${this.appkey}`;
if (this.libraries.length) {
url += `&libraries=${this.libraries.join(",")}`;
}
url += `&autoload=false`;
return url;
}
deleteScript() {
const script = document.getElementById(this.id);
if (script) {
script.remove();
}
}
callback() {
kakao.maps.load(() => {
Loader.instance.done = true;
Loader.instance.loading = false;
Loader.instance.callbacks.forEach(cb => {
cb(Loader.instance.onEvent);
});
Loader.instance.callbacks = [];
Loader.loadEventCallback.forEach(cb => {
cb(Loader.instance.onEvent);
});
});
}
static equalOptions(a, b) {
if (a.appkey !== b.appkey) return false;
if (a.id !== b.id) return false;
if (a.libraries.length !== b.libraries.length) return false;
for (let i = 0; i < a.libraries.length; ++i) {
if (a.libraries[i] !== b.libraries[i]) return false;
}
if (a.nonce !== b.nonce) return false;
if (a.retries !== b.retries) return false;
if (a.url !== b.url) return false;
return true;
}
}
export { Loader, LoaderStatus };